react-hook-form自定义组件数组值处理:复选框选中状态丢失
问题
我使用react-hook-form的Controller组件,将字段与ref传入自定义组件以展示复选框数组,选中项对应为对象数组元素。组件渲染正常,但勾选复选框后,表单刷新会导致选中状态丢失。以下是我的自定义组件及表单代码,请问问题出在哪里?
自定义组件代码
type FormCheckBoxesParts = { options: Quality[]; onChange: (...event: any[]) => void; value: Quality[] | undefined; name: string; ref: Ref<Array<Quality>>; } const handleTraitCheck = (value: Spec[], checkedName: Spec) => { const existingValue = value.filter(qi => qi.name === checkedName.name); if (existingValue && existingValue.length === 1) { // then we are unchecking, so remove element from value array value.splice(value.indexOf(existingValue[0]),1) } else { // then we are checking, so add element from value array value.push(checkedName); } }; export const SpecArrayCheckboxes: FunctionComponent<FormCheckBoxesParts> = forwardRef(({ onChange, value, name, options }, ref) => { return ( <ThemeProvider theme={strikerTheme}> <h5>{name}</h5> { options.map((option: Quality) => { return ( <Tooltip title={option.description}> <FormControlLabel ref={ref} key={option.name} label={option.name} control={ <Checkbox checked={value && value.some((fo: Quality) => fo.name === option.name)} onChange={() => value && onChange(handleTraitCheck(value, option))} /> } /> </Tooltip> ); }) } </ThemeProvider> ) });
表单代码
export const DescriptionForm = ({ onSubmit, data }: { data: Partial<Char>; onSubmit: SubmitHandler<Partial<Char>>; }) => { const { specState } = useAppSelector(state => state); const { register, handleSubmit, control } = useForm({ defaultValues: { ...data, } }); const alignments = ['Lawful Good', 'Neutral Good', 'Chaotic Good', 'Lawful Neutral', 'Neutral', 'Chaotic Neutral', 'Lawful Evil', 'Neutral Evil', 'Chaotic Evil'] const traits: Quality[] = getTraits(specState.specKeys); const ideals: Quality[] = getIdeals(specState.specKeys); return ( <ThemeProvider theme={strikerTheme}> <form className='form' onSubmit={handleSubmit(onSubmit)}> <h4>Description</h4> <div className='form-row'> <Controller name="name" control={control} render={({ field }) => <TextField sx={{ width: '40ch' }} {...field} label="character name" />} /> </div> <div className='form-row'> <Controller name="experience" control={control} render={({ field }) => <TextField {...field} label="experience" />} /> <Controller name="levels" control={control} render={({ field }) => <TextField {...field} label="level" />} /> </div> <div className='form-row'> <Controller name="age" control={control} render={({ field }) => <TextField {...field} label="age" />} /> <Controller name="gender" control={control} render={({ field }) => <TextField {...field} label="gender" />} /> <Controller name="height" control={control} render={({ field }) => <TextField {...field} label="height" />} /> <Controller name="weight" control={control} render={({ field }) => <TextField {...field} label="weight" InputProps={{ endAdornment: <InputAdornment position="end">kg</InputAdornment>, }} />} /> </div> <div className='form-row'> <Controller name="alignment" control={control} render={({ field }) => <TextField {...field} label="alignment" helperText="Please select your alignment" select> {alignments.map((option) => ( <MenuItem key={option} value={option}> {option} </MenuItem> ))} </TextField>} /> <div className='form-row'> <Controller name="traits" control={control} render={({ field }) => <SpecArrayCheckboxes {...field} options={traits}/> } /> </div> <div className='form-row'> <Controller name="ideals" control={control} render={({ field }) => <SpecArrayCheckboxes {...field} options={ideals}/> } /> </div> </div> <button type='submit' className='btn btn-block btn-danger' > Submit </button> </form> </ThemeProvider> ) };
问题原因与解决方案
核心问题
- 可变操作导致状态无法追踪:
handleTraitCheck里用splice和push直接修改了react-hook-form管理的value数组,这是可变操作。react-hook-form依赖不可变更新感知状态变化,因此不会触发表单状态同步,刷新后选中状态丢失。 - onChange未传递有效新值:
handleTraitCheck没有返回新数组,onChange实际接收到的是undefined,根本没把更新后的状态同步给表单。 - ref使用错误:给每个
FormControlLabel绑定同一个ref,不仅无效还会导致ref被覆盖;且react-hook-form的Controller已通过field.ref处理表单引用,无需手动传递给单个复选框。 - 类型不一致:
handleTraitCheck参数用了Spec类型,但组件实际使用Quality类型,需要统一。
修正后的自定义组件代码
import { forwardRef, Ref, FunctionComponent } from 'react'; import { ThemeProvider } from '@mui/material/styles'; import Tooltip from '@mui/material/Tooltip'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; // 统一类型,根据实际业务调整属性 type Quality = { name: string; description: string; // 其他属性 }; type FormCheckBoxesParts = { options: Quality[]; onChange: (newValue: Quality[]) => void; value: Quality[] | undefined; name: string; }; // 不可变更新:返回新数组,不修改原数组 const handleTraitCheck = (currentValue: Quality[], option: Quality): Quality[] => { const isChecked = currentValue.some(item => item.name === option.name); if (isChecked) { return currentValue.filter(item => item.name !== option.name); } else { return [...currentValue, option]; } }; export const SpecArrayCheckboxes = forwardRef<unknown, FormCheckBoxesParts>(({ onChange, value, name, options }, ref) => { // 处理value为undefined的情况,默认空数组 const currentValue = value || []; return ( <ThemeProvider theme={strikerTheme}> <h5>{name}</h5> {options.map((option) => ( <Tooltip key={option.name} title={option.description}> <FormControlLabel label={option.name} control={ <Checkbox checked={currentValue.some(item => item.name === option.name)} onChange={() => onChange(handleTraitCheck(currentValue, option))} /> } /> </Tooltip> ))} </ThemeProvider> ); }); SpecArrayCheckboxes.displayName = 'SpecArrayCheckboxes';
关键说明
- 不可变更新:每次状态变化返回新数组,让react-hook-form能正确追踪状态,确保选中状态同步。
- 正确传递新值:
onChange直接接收handleTraitCheck返回的新数组,同步更新表单状态。 - 移除无效ref:删除单个复选框上的ref绑定,依赖Controller自动处理表单引用。
- 类型统一:修正
Spec与Quality的类型不一致问题,避免类型错误。
内容的提问来源于stack exchange,提问作者Michael Coxon
相关产品推荐
相关产品推荐

