You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:05:09