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

如何在React Hook Form中处理动态增减的组件?

用react-hook-form处理动态增删的表单组件列表

核心方案:使用useFieldArray钩子

react-hook-form专门提供了useFieldArray来处理动态数组类型的表单字段,它能自动同步列表增删操作与表单状态,无需手动维护组件列表和表单数据的一致性。

步骤1:改造父组件

  1. 引入useForm和useFieldArray钩子
  2. 初始化表单时设置数组字段的默认值
  3. 用useFieldArray返回的fields、append、remove替代手动维护的组件列表状态
  4. 遍历fields渲染子组件,传递表单方法和必要参数

改造后的父组件代码:

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

// 定义表单数据类型
type FormValues = {
  sections: Array<{
    insideDiameter: string;
    outsideDiameter: string;
  }>;
};

export default function ParentComponent() {
  const { register, control, handleSubmit } = useForm<FormValues>({
    defaultValues: {
      // 初始化一个默认的section
      sections: [{ insideDiameter: "", outsideDiameter: "" }],
    },
  });

  // 使用useFieldArray管理sections数组
  const { fields, append, remove } = useFieldArray({
    control,
    name: "sections", // 对应表单数据里的数组字段名
  });

  // 提交表单时获取完整数据
  const onSubmit = (data: FormValues) => {
    console.log("表单数据:", data);
    // 此处可处理提交逻辑,比如发送请求
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => (
        <SectionItem
          key={field.id} // useFieldArray生成的唯一id,比index更稳定
          index={index}
          register={register}
          removeSection={() => remove(index)}
          isOnlySection={fields.length === 1}
          addSection={() => append({ insideDiameter: "", outsideDiameter: "" })}
        />
      ))}

      <button type="submit" style={{ marginTop: "20px" }}>提交表单</button>
    </form>
  );
}

步骤2:改造子组件SectionItem

  1. 接收父组件传递的register、index、增删方法
  2. 给每个输入框的name设置为数组名[索引].字段名的格式,让react-hook-form识别为数组元素的属性
  3. 绑定增删按钮的点击事件到对应方法

改造后的子组件代码:

import { TextField, Typography, Grid, Fab, AddIcon, RemoveIcon } from "@mui/material";
import { UseFormRegister } from "react-hook-form";
import type { FormValues } from "./ParentComponent"; // 导入表单类型

type SectionItemProps = {
  index: number;
  register: UseFormRegister<FormValues>;
  addSection: () => void;
  removeSection: () => void;
  isOnlySection: boolean;
};

export const SectionItem: React.FC<SectionItemProps> = ({
  index,
  register,
  addSection,
  removeSection,
  isOnlySection,
}) => {
  return (
    <>
      <Typography variant="h6" gutterBottom sx={{ marginTop: 3 }}>
        {`Участок № ${index + 1}`} {/* 索引从0开始,加1显示序号 */}
      </Typography>
      <Grid container spacing={3} sx={{ marginTop: 1 }}>
        <Grid item xs={12} sm={6}>
          <TextField
            required
            id={`insideDiameter-${index}`}
            {...register(`sections[${index}].insideDiameter`, {
              required: "此字段为必填项",
            })}
            fullWidth
            variant="standard"
            label="内径"
          />
        </Grid>
        <Grid item xs={12} sm={6}>
          <TextField
            required
            id={`outsideDiameter-${index}`}
            {...register(`sections[${index}].outsideDiameter`, {
              required: "此字段为必填项",
            })}
            fullWidth
            variant="standard"
            label="外径"
          />
        </Grid>
      </Grid>
      <Grid container spacing={3} sx={{ marginTop: 1 }}>
        <Grid item xs={12} sm={12} style={{ textAlign: "center" }}>
          <Fab size="small" aria-label="add" onClick={addSection}>
            <AddIcon color="primary" />
          </Fab>
          {!isOnlySection && (
            <Fab size="small" aria-label="remove" sx={{ marginLeft: 1 }} onClick={removeSection}>
              <RemoveIcon color="primary" />
            </Fab>
          )}
        </Grid>
      </Grid>
    </>
  );
};

关键说明

  • useFieldArray的name参数必须和表单数据里的数组字段名完全一致(此处为sections)
  • 使用field.id作为组件key,避免因索引变化导致的组件不必要重渲染
  • 表单提交时,data.sections会自动包含所有动态添加的section的输入值,无需手动收集

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:55:44