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

如何在react-hook-form中更新嵌套数组结构的表单数据?

React Hook Form嵌套数组表单数据无法正确传入数据库问题排查

最近在使用react-hook-form处理嵌套数组结构的表单数据更新时,遇到了数据无法正确传入数据库的问题——无报错但提交数据异常。目标是通过useFieldArray钩子实现嵌套数组的表单数据更新,以下是相关代码及排查修复方案:


字段结构定义

export const defects = {
  title: 'Defects',
  fields: [
    {
      title: '缺陷位置:',
      name: 'defects.location',
      type: 'checkbox',
      fields: [
        {
          title: '室内',
          type: 'checkbox',
          name: 'defects.location.interior',
        },
        {
          title: '屋顶空间',
          type: 'checkbox',
          name: 'defects.location.roofSpace',
        },
        {
          title: '其他',
          type: 'textarea',
          name: 'defects.location.other',
        },
      ],
    },
    {
      title: '缺陷摘要:',
      name: 'defects.summary',
      type: 'checkbox',
      fields: [
        {
          title: '屋顶框架',
          type: 'checkbox',
          name: 'defects.summary.roofFraming',
        },
        {
          title: '门廊支撑',
          type: 'checkbox',
          name: 'defects.summary.porchSupport',
        },
        {
          title: '户外区域支撑',
          type: 'checkbox',
          name: 'defects.summary.alfrescoSupport',
        },
        {
          title: '大门结构',
          type: 'checkbox',
          name: 'defects.summary.gateFeature',
        },
        {
          title: '底层地板结构',
          type: 'checkbox',
          name: 'defects.summary.subfloorStructure',
        },
        {
          title: '砖块',
          type: 'checkbox',
          name: 'defects.summary.brick',
        },
        {
          title: '其他',
          type: 'textarea',
        },
      ],
    },
  ],
}

父组件代码

const { register, handleSubmit, errors, setValue, getValues, control, watch, reset } = useForm({ shouldUnregister: false })

const { fields, append, remove, update } = useFieldArray({
  control: control,
  name: defects.title,
})

const onSubmit = async (data) => {
  const values = getValues()
  console.log('提交数据', data, values)
  const response = await fetch('/api/db/handler', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  })
  if (response.ok) {
    setSaved(true)
    setTimeout(() => {
      setSaved(false)
    }, 2000)
  }
  return response
}

return(
  <Defects
    data={defects}
    register={register}
    setValue={setValue}
    control={control}
    watch={watch}
    fields={fields}
    append={append}
    remove={remove}
    update={update}
  />
)

Defect组件代码

const Defects = ({
  register,
  setValue,
  control,
  watch,
  data,
  fields,
  append,
  remove,
  update,
}) => {
  useEffect(() => {
    update();
  }, [register]);

  return (
    <div>
     
      {fields.map((props, index) => {
        return (
          <Disclosure
            defaultOpen
            open={true}
            as='div'
            key={props.id}
            className='border-b-4 p-4 border-gray-100'
          >
            {index > 0 ? (
              <div className='flex items-center'>
                <Disclosure.Button className='text-xl uppercase underline flex items-center'>
                  {`缺陷 ${index}`}
                  <FaChevronDown className='ml-4 ui-open:rotate-180 ui-open:transform' />
                </Disclosure.Button>
                <button
                  className='bg-sky-400 text-white px-4 py-2 rounded mt-4 ml-12'
                  onClick={() => remove(index)}
                >
                  删除
                </button>
              </div>
            ) : (
              <div className='flex items-center'>
                <Disclosure.Button className='text-xl uppercase underline flex items-center'>
                  缺陷列表
                  <FaChevronDown className='ml-4 ui-open:rotate-180 ui-open:transform' />
                </Disclosure.Button>
              </div>
            )}
            {props.fields.map((field, i) => {
              return (
                <Disclosure.Panel
                  key={field.title + i}
                  className='grid grid-cols-[minmax(min-content,.5fr)_minmax(300px,1fr)] my-4'
                >
                  {field.sectionTitle && (
                    <h3 className='col-span-2 text-lg border-b-4 mb-2 border-b-gray-100 uppercase'>
                      {field.sectionTitle}
                    </h3>
                  )}
                  <h3>{field.title}</h3>
                 
                  {field.type == 'checkbox' && (
                    <Checkbox
                      data={field}
                      register={register}
                      setValue={setValue}
                      nestedIndex={index}
                      fieldName={field.name}
                      fieldIndex={i}
                    />
                  )}
                
                </Disclosure.Panel>
              );
            })}
          </Disclosure>
        );
      })}
    </div>
  );
};

Checkbox组件代码

import React from 'react';

const Checkbox = ({  data,
  register,
  setValue,
  nestedIndex = null,
  fieldName = '',
  fieldIndex = null,}) => {
  return (
    <div className='flex flex-col justify-start items-start'>
      {data.fields.map((item, index) => {
            return (
              <div
                key={`${item.title}${index}`}
                className='flex flex-row items-center'
              >
                <label className='mr-2' htmlFor={item.name}>
                  {item.title}
                </label>
                <input
                  type={item.type}
                  name={`Defects[${nestedIndex}].fields[${fieldIndex}].fields[${index}].${item.name}`}
                  className={`${
                    item.type == 'textarea' && 'w-full'
                  } border border-gray-100 p-2`}
                  {...register(
                    `Defects[${nestedIndex}].fields[${fieldIndex}].fields[${index}].${item.name}`
                  )}
                />
              </div>
            );
          })}
</div>
);
}

问题根源分析

  1. useFieldArray初始化缺失默认值:未给嵌套数组设置匹配结构的初始值,导致fields结构不匹配,无法正确绑定表单控件。
  2. 表单控件name路径冗余:Checkbox组件中item.name本身包含defects.location.interior这类前缀,拼接后路径变成Defects[0].fields[0].fields[0].defects.location.interior,结构混乱。
  3. update方法调用时机错误:Defects组件中依赖register调用update(),但register是稳定引用,会触发不必要的无效更新。
  4. 字段命名冲突:字段结构中的name属性(如defects.location)和顶层数组名称Defects重复,导致表单数据结构混乱。

修复方案

1. 给useForm设置匹配的初始值

在父组件的useForm中添加默认值,对齐嵌套数组结构:

const { register, handleSubmit, errors, setValue, getValues, control, watch, reset } = useForm({
  shouldUnregister: false,
  defaultValues: {
    Defects: [
      {
        fields: [
          {
            fields: [
              { interior: false },
              { roofSpace: false },
              { other: '' }
            ]
          },
          {
            fields: [
              { roofFraming: false },
              { porchSupport: false },
              { alfrescoSupport: false },
              { gateFeature: false },
              { subfloorStructure: false },
              { brick: false },
              { other: '' }
            ]
          }
        ]
      }
    ]
  }
})

2. 修正Checkbox组件的name路径

通过字符串截取获取纯字段名,避免冗余前缀:

const Checkbox = ({  data,
  register,
  setValue,
  nestedIndex = null,
  fieldName = '',
  fieldIndex = null,}) => {
  return (
    <div className='flex flex-col justify-start items-start'>
      {data.fields.map((item, index) => {
            // 截取纯字段名,去掉前缀
            const pureFieldName = item.name?.split('.').pop() || `other_${index}`;
            return (
              <div
                key={`${item.title}${index}`}
                className='flex flex-row items-center'
              >
                <label className='mr-2' htmlFor={pureFieldName}>
                  {item.title}
                </label>
                <input
                  type={item.type}
                  name={`Defects[${nestedIndex}].fields[${fieldIndex}].fields[${index}].${pureFieldName}`}
                  className={`${
                    item.type == 'textarea' && 'w-full'
                  } border border-gray-100 p-2`}
                  {...register(
                    `Defects[${nestedIndex}].fields[${fieldIndex}].fields[${index}].${pureFieldName}`
                  )}
                />
              </div>
            );
          })}
</div>
);
}

3. 移除无效的useEffect

删除Defects组件中依赖register的useEffect,update方法仅在需要手动更新字段时调用:

// 移除以下代码
useEffect(() => {
  update();
}, [register]);

4. 统一字段命名规范(可选)

将字段结构中的name改为纯标识,避免和顶层数组名称冲突:

// 示例修改
{
  title: '缺陷位置:',
  name: 'location',
  type: 'checkbox',
  fields: [
    {
      title: '室内',
      type: 'checkbox',
      name: 'interior',
    },
    // ...其他字段
  ]
}

5. 确保useFieldArray的name与默认值一致

父组件中useFieldArray的name需和defaultValues中的顶层数组键完全匹配:

const { fields, append, remove, update } = useFieldArray({
  control: control,
  name: 'Defects',
})

内容的提问来源于stack exchange,提问作者Nayan Bhakhar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 08:50:32