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

React Hook Form跨组件校验:子组件验证父级表单数据

分节验证动态表单的实现方案

基于react-hook-form和useFieldArray,你可以通过针对性触发指定字段验证的方式实现分节校验需求,核心思路是把每个Section对应表单的嵌套字段组,用trigger方法单独验证前一节的所有字段。

核心实现步骤

1. 定义嵌套式表单结构

将每个Section的数据封装为表单的嵌套对象,动态字段用数组存储,示例结构如下:

const defaultValues = {
  section1: {
    fields: [{ name: '', value: '' }] // Section1的动态字段初始值
  },
  section2: {
    fields: [{ item: '', quantity: '' }] // Section2的动态字段初始值
  },
  // 更多Section可按此格式扩展
};

2. 初始化useForm并配置验证规则

在根组件中初始化useForm,为每个Section的字段设置验证规则,同时用useFieldArray管理各Section的动态字段:

import { useForm, useFieldArray, useState } from 'react-hook-form';

function App() {
  const { register, control, trigger, getValues, formState: { errors } } = useForm({
    defaultValues,
    mode: 'onChange' // 可选,开启实时字段验证
  });

  // 初始化Section1的动态字段管理
  const { fields: section1Fields, append: appendSection1, remove: removeSection1 } = useFieldArray({
    control,
    name: 'section1.fields'
  });

  // 初始化Section2的动态字段管理
  const { fields: section2Fields, append: appendSection2, remove: removeSection2 } = useFieldArray({
    control,
    name: 'section2.fields'
  });

  // 控制Section的显示状态
  const [showSection2, setShowSection2] = useState(false);
  const [showSection3, setShowSection3] = useState(false);

3. 封装Section组件(复用增删与验证逻辑)

把每个Section的渲染、动态字段增删、前节验证逻辑封装成可复用组件:

function Section({ 
  prevSectionName, 
  currentSectionName, 
  fields, 
  append, 
  remove, 
  register, 
  trigger, 
  getValues, 
  errors, 
  onValidatePass 
}) {
  const handleValidatePrev = async () => {
    // 触发前一节所有字段的验证
    const isValid = await trigger(prevSectionName);
    if (isValid) {
      // 验证通过后,可获取前一节数据做提交或其他处理
      const prevData = getValues(prevSectionName);
      console.log(`${prevSectionName} 数据:`, prevData);
      // 执行后续逻辑,比如显示下一节
      onValidatePass?.();
    }
  };

  return (
    <div className="section">
      <h3>{currentSectionName}</h3>
      {/* 渲染动态字段组 */}
      {fields.map((item, index) => (
        <div key={item.id} className="field-row">
          {/* Section1专属字段 */}
          {currentSectionName === 'Section 1' && (
            <>
              <input
                {...register(`section1.fields.${index}.name`, {
                  required: '名称不能为空'
                })}
                placeholder="名称"
              />
              {errors?.section1?.fields?.[index]?.name && (
                <span className="error-text">{errors.section1.fields[index].name.message}</span>
              )}
              <input
                {...register(`section1.fields.${index}.value`, {
                  required: '值不能为空',
                  pattern: { value: /^\d+$/, message: '必须是数字' }
                })}
                placeholder="数值"
              />
              {errors?.section1?.fields?.[index]?.value && (
                <span className="error-text">{errors.section1.fields[index].value.message}</span>
              )}
            </>
          )}
          {/* Section2专属字段 */}
          {currentSectionName === 'Section 2' && (
            <>
              <input
                {...register(`section2.fields.${index}.item`, {
                  required: '项目不能为空'
                })}
                placeholder="项目名称"
              />
              {errors?.section2?.fields?.[index]?.item && (
                <span className="error-text">{errors.section2.fields[index].item.message}</span>
              )}
              <input
                {...register(`section2.fields.${index}.quantity`, {
                  required: '数量不能为空',
                  min: { value: 1, message: '数量不能小于1' }
                })}
                type="number"
                placeholder="数量"
              />
              {errors?.section2?.fields?.[index]?.quantity && (
                <span className="error-text">{errors.section2.fields[index].quantity.message}</span>
              )}
            </>
          )}
          <button type="button" onClick={() => remove(index)}>删除</button>
        </div>
      ))}
      <button type="button" onClick={() => append({})}>添加字段</button>
      
      {/* 仅非首节显示验证前节的按钮 */}
      {prevSectionName && (
        <button type="button" className="validate-btn" onClick={handleValidatePrev}>
          验证{prevSectionName}并继续
        </button>
      )}
    </div>
  );
}

4. 在根组件中渲染各Section并控制显示

通过状态控制Section的显示,只有前一节验证通过后才展示下一节:

return (
    <form className="form-container">
      <Section
        prevSectionName={null}
        currentSectionName="Section 1"
        fields={section1Fields}
        append={appendSection1}
        remove={removeSection1}
        register={register}
        trigger={trigger}
        getValues={getValues}
        errors={errors}
        onValidatePass={() => setShowSection2(true)}
      />

      {showSection2 && (
        <Section
          prevSectionName="section1"
          currentSectionName="Section 2"
          fields={section2Fields}
          append={appendSection2}
          remove={removeSection2}
          register={register}
          trigger={trigger}
          getValues={getValues}
          errors={errors}
          onValidatePass={() => setShowSection3(true)}
        />
      )}

      {/* Section3及后续可按相同方式扩展 */}
    </form>
  );
}

关键细节说明

  • 精准验证:trigger('section1')会自动验证section1下的所有字段,包括useFieldArray生成的动态字段,无需手动遍历。
  • 错误处理:通过errors.sectionX.fields[index].fieldName的方式访问动态字段的错误信息,使用可选链避免空值报错。
  • 分步数据处理:验证通过后,可调用getValues('sectionX')获取该节的完整数据,单独提交或缓存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:42:20