查閱 Chakra UI 和 React Hook Form 的文件,已經能夠滿足 8 成的使用情況,但是,在建立表單元件 <Radio />
和 <Checkbox />
,就沒那麼順利了,在文件上找不到如何搭配使用。
使用環境
"next": ^13.1.1,
"@chakra-ui/react": ^2.4.6,
"react-hook-form": ^7.41.5
Chakra UI
React Hook Form
接下來的內容
- 一般用法
Radio
和Checkbox
的用法name
和錯誤訊息的資料格式- 可能會沒注意到的地方
一般用法
使用 <Input />
的表單,只要按照 React Hook Form 的說明,加上 register()
即可:
import { useForm } from 'react-hook-form';
import { Input } from '@chakra-ui/react';
const { register } = useForm({ defaultValues });
<Input {...register('name', { required: true })} />
文件上說明得很清楚,就不再贅述。
Radio 和 Checkbox 的用法
Chakra UI 的 Radio
、Checkbox
通常不是如 html 表單直接使用,而是有語法規則:
import { RadioGroup, Radio, CheckboxGroup, Checkbox, Stack } from '@chakra-ui/react';
<RadioGroup>
<Stack>
<Radio>選項一</Radio>
<Radio>選項二</Radio>
<Radio>選項三</Radio>
</Stack>
</RadioGroup>
<CheckboxGroup>
<Stack>
<Checkbox>選項一</Checkbox>
<Checkbox>選項二</Checkbox>
<Checkbox>選項三</Checkbox>
</Stack>
</CheckboxGroup>
試過很多方法,卻總是在與 defaultValues
相關的情境不如預期。
// ❌ 會出現錯誤的語法
// 在 Group 層級加上 register()
<RadioGroup {...register('range')}>
<Stack>
...
</Stack>
</RadioGroup>
<CheckboxGroup {...register('option')}>
<Stack>
...
</Stack>
</CheckboxGroup>
// 或在選項加上 register()
<RadioGroup>
<Stack>
<Radio {...register('range')} value={0}>
選項一
</Radio>
<Radio {...register('range')} value={1}>
選項二
</Radio>
<Radio {...register('range')} value={2}>
選項三
</Radio>
</Stack>
</RadioGroup>
<CheckboxGroup>
<Stack>
<Checkbox {...register('option')} value={0}>
選項一
</Checkbox>
<Checkbox {...register('option')} value={1}>
選項二
</Checkbox>
<Checkbox {...register('option')} value={2}>
選項三
</Checkbox>
</Stack>
</CheckboxGroup>
正確的方法是使用 React Hook Form 的 Controller
:
// ✅ 正確的語法
import { useForm, Controller } from 'react-hook-form';
import { RadioGroup, Radio, CheckboxGroup, Checkbox, Stack } from '@chakra-ui/react';
const { control } = useForm({ defaultValues });
<Controller
name="range" //改用 name
control={control}
render={({ field }) => (
<RadioGroup {...field}>
<Stack>
<Radio value={0}>選項一</Radio>
<Radio value={1}>選項二</Radio>
<Radio value={2}>選項三</Radio>
</Stack>
</RadioGroup>
)}
/>
<Controller
name="option" //改用 name
control={control}
render={({ field }) => (
<RadioGroup {...field}>
<Stack>
<Radio value={0}>選項一</Radio>
<Radio value={1}>選項二</Radio>
<Radio value={2}>選項三</Radio>
</Stack>
</RadioGroup>
)}
/>
name
和錯誤訊息的資料格式
React Hook Form 設定表單物件層級的方法是在 register()
裡面,以 .
分開的字串;而錯誤訊息是物件。
這個不同之處,會在表單有許多欄位,且層級不同時出現問題:
const { register, formState: { errors } } = useForm({ defaultValues });
import { FormControl, Input, FormErrorMessage } from '@chakra-ui/react';
const fields = [
{
name: 'displayName',
rules: {
required: {
value: true,
message: '必填'
}
}
},
{
name: 'subscription.months',
rules: {
max: {
value: 12,
message: '最多 12 個月'
}
}
},
{
name: 'company.id',
rules: {
maxLength: {
value: 20,
message: '最多 20 個字'
}
}
}
];
// ...
{fields.map(({ name, rules }) =>
// ❌ 只有第 1 組欄位會在不符規則時,顯示錯誤訊息
<FormControl isInvalid={errors[name]}>
<Input {...register(name, { ...rules })} />
<FormErrorMessage>{!!errors[name] && errors[name].message}</FormErrorMessage>
</FormControl>
)}
其實 React Hook Form 文件上有提到該怎麼辦,只是擺在很偏僻的地方,非常容易錯過。
最簡單的方法是用 lodash
的 get()
,就可以把 subscription.months
之類的格式變成物件:
import get from 'lodash/get';
//...
{fields.map(({ name, rules }) => {
// ✅ 取得 errors 物件裡,對應的 value 和 message
const error = get(errors, name);
return (
<FormControl isInvalid={error}>
<Input {...register(name, { ...rules })} />
<FormErrorMessage>{!!error && error.message}</FormErrorMessage>
</FormControl>
)}
)}
可能會沒注意到的地方
React Hook Form 儲存資料的格式一律是字串。
所以在使用 <Radio />
或 <Checkbox />
的時候,若把 value
值指定為數字,並設定 defaultValue
:
// ⚠️ 選項一應要預設選取
import { useForm, Controller } from 'react-hook-form';
import { RadioGroup, Radio, Stack } from '@chakra-ui/react';
const { control } = useForm({ defaultValues });
<Controller
name="range"
control={control}
render={({ field }) => (
<RadioGroup {...field} defaultValue={0}>
<Stack>
<Radio value={0}>選項一</Radio>
<Radio value={1}>選項二</Radio>
<Radio value={2}>選項三</Radio>
</Stack>
</RadioGroup>
)}
/>
會發現無法讓 value
是 0
的選項一
預設選取。
需把是數字的 defaultValue
轉為字串:
// ✅ 正確的語法(之一)
defaultValue={`${0}`}