React Hook Form字段数组Dirty State与表单数据不同步问题
React Hook Form Field Array 脏状态与表单数据不同步问题解决方案
这个问题的核心原因是:React Hook Form 的脏状态(dirty state)是基于字段路径的索引标记的,而非数组项的唯一标识。当删除数组中靠前的项时,后续项的索引会前移,但脏状态仍保留原索引路径的标记,导致和实际表单数据结构不匹配。
最优解决方法:用字段唯一ID绑定路径,替代动态索引
React Hook Form 的 useFieldArray 返回的 fields 数组中,每个项都自带内部生成的唯一 id,这个ID不会随数组顺序变化而改变。我们可以用这个 id 替代动态索引作为字段路径标识,让脏状态绑定到具体数组项,而非位置索引。
修改后的代码如下:
const BasicForm = () => { const { control, handleSubmit, formState: { errors, dirtyFields } } = useForm({ defaultValues: { requirements: [{ description: '' }], }, }); const { fields, append, remove } = useFieldArray({ control, name: 'requirements', }); const onSubmit = (data) => { console.log('Form data', data); console.log('Dirty fields', dirtyFields); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {fields.map((field) => ( <div key={field.id} style={{ marginBottom: '10px' }}> <Controller control={control} // 用field.id作为路径标识,替代动态索引 name={`requirements.${field.id}.description`} render={({ field: inputField }) => ( <input {...inputField} placeholder="Requirement" style={{ marginRight: '10px' }} /> )} /> <button type="button" onClick={() => remove(field.id)}>Remove</button> </div> ))} <button type="button" onClick={() => append({ description: '' })}> Add Requirement </button> <button type="submit">Submit</button> </form> ); };
关键修改点:
- Controller 的
name从依赖index改为依赖field.id,确保字段路径唯一且稳定 - 删除操作从
remove(index)改为remove(field.id),直接通过唯一ID删除对应项
修改后,无论数组项顺序如何变化,脏状态都会准确绑定到对应数组项,不会出现索引错位导致的不同步问题。
备选方案:手动修正脏状态
如果无法修改字段路径命名方式,可以在删除操作后手动调用 setDirtyFields 调整脏状态:
import { useForm, useFieldArray } from "react-hook-form"; const BasicForm = () => { const { control, handleSubmit, formState: { errors, dirtyFields }, setDirtyFields } = useForm({ defaultValues: { requirements: [{ description: '' }], }, }); const { fields, append, remove } = useFieldArray({ control, name: 'requirements', }); const handleRemove = (index) => { // 执行删除操作 remove(index); // 手动更新脏状态:过滤已删除索引的脏字段,并重排剩余字段索引 const newDirtyFields = { ...dirtyFields }; if (newDirtyFields.requirements) { const updatedRequirements = Object.entries(newDirtyFields.requirements) .filter(([key]) => parseInt(key) !== index) .map(([key, value]) => ({ [parseInt(key) > index ? parseInt(key) - 1 : key]: value })) .reduce((acc, curr) => ({ ...acc, ...curr }), {}); newDirtyFields.requirements = updatedRequirements; setDirtyFields(newDirtyFields); } }; const onSubmit = (data) => { console.log('Form data', data); console.log('Dirty fields', dirtyFields); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {fields.map((field, index) => ( <div key={field.id} style={{ marginBottom: '10px' }}> <Controller control={control} name={`requirements.${index}.description`} render={({ field: inputField }) => ( <input {...inputField} placeholder={`Requirement ${index + 1}`} style={{ marginRight: '10px' }} /> )} /> <button type="button" onClick={() => handleRemove(index)}>Remove</button> </div> ))} <button type="button" onClick={() => append({ description: '' })}> Add Requirement </button> <button type="submit">Submit</button> </form> ); };
这个方案通过手动调整脏字段索引使其匹配删除后的数组结构,但相比第一种方案更繁琐,推荐优先使用基于唯一ID的方案。
内容的提问来源于stack exchange,提问作者Luke Sharon
相关产品推荐
相关产品推荐

