網頁常見的功能之一就是表單,像是註冊、輸入個人資料、後臺建立管理資料等都需要有表單輸入的功能,另外最好還有前端能事先對表單的輸入內容進行檢查,通過後才發給後端。
為了達成這些功能我常用的組合是 react-hook-form 搭配 yup ,首先安裝套件。
pnpm add -D react-hook-form @hookform/resolvers yup
示範一個基礎應用:
'use client';
import { useForm, Controller } from 'react-hook-form';
import TextField from '@mui/material/TextField';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { Button } from '@mui/material';
export default async function ContactForm() {
const { control, handleSubmit } = useForm({
resolver: yupResolver(
yup.object({
email: yup.string().email().required(),
})
),
});
const onSubmit = handleSubmit((data) => {
console.log(data);
});
return (
<div>
<Controller
name="email"
control={control}
defaultValue=""
render={({ field, fieldState }) => (
<TextField
label="Email"
{...field}
error={!!fieldState.error}
helperText={fieldState.error ? fieldState.error.message : null}
/>
)}
/>
<Button onClick={onSubmit}>Submit</Button>
</div>
);
}
解說開始,首先是 useForm
這個 hook,是整個表單的集中管理區,搜集表單內容、檢查、送出都是經由這邊提供的 api。
而因為要搭配 MUI 使用,所以藉由 Controller
這個元件將表單控制的相關內容提供給 MUI 的元件。
主要的控制項會是 field
當中的 value
跟 onChange
,因為 key 名稱完全相同所以這邊用 {...field}
的寫法偷行數,實際展開來會像這樣:
<Controller
name="email"
control={control}
defaultValue=""
render={({ field: { value, onChange }, fieldState }) => (
<TextField
label="Email"
value={value}
onChange={onChange}
error={!!fieldState.error}
helperText={fieldState.error ? fieldState.error.message : null}
/>
)}
/>
再來 useForm
上可以定義整個表單的格式驗證,這邊用的 yup ,而 react-hook-form 有特地為了 yup 製作轉換函式,將 yup 產生的檢查格式轉換成 react-hook-form 能使用的格式。
const { control, handleSubmit } = useForm({
resolver: yupResolver(
yup.object({
email: yup.string().email().required(),
})
),
});
如果欄位的格式驗證有誤,就能經由 Controller 的 fieldState 取得該欄位的錯誤訊息。
最後要將表單內容送出的話是經由 handleSubmit
進行。
handleSubmit
的第一個參數是當驗證通過後處理表單資訊的 callback ,通常就在這邊去呼叫 api。
另外 handleSubmit
的第二個參數是當格式驗證失敗後的處理,雖然錯誤訊息可以直接經由 Controller 提供所以這裡通常用不太上,但如果要觸發跳窗或通知的話可以在這邊處理。
const onSubmit = handleSubmit(
(data) => {
console.log(data);
},
(error) => {
console.log(error);
}
);
react-hook-form 在使用上可以相當靈活,尤其是遇到一些樣式較複雜的表單的時候,可以將表單的各個區塊切出元件但保持 useForm 的集中管理功能,主要靠的是 useFormContext
,對於程式碼切分非常方便。
'use client';
import {
useForm,
Controller,
useFormContext,
FormProvider,
} from 'react-hook-form';
import TextField from '@mui/material/TextField';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { Button } from '@mui/material';
function Field() {
const methods = useFormContext();
return (
<Controller
name="email"
control={methods.control}
defaultValue=""
render={({ field, fieldState }) => (
<TextField
label="Email"
{...field}
error={!!fieldState.error}
helperText={fieldState.error ? fieldState.error.message : null}
/>
)}
/>
);
}
export default async function ContactForm() {
const methods = useForm({
resolver: yupResolver(
yup.object({
email: yup.string().email().required(),
})
),
});
const onSubmit = methods.handleSubmit(
(data) => {
console.log(data);
},
(error) => {
console.log(error);
}
);
return (
<div>
<FormProvider {...methods}>
<Field />
</FormProvider>
<Button onClick={onSubmit}>Submit</Button>
</div>
);
}