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

如何使用React Hook Form处理对象数组的表单输入与提交?

实现React Hook Form数组对象输入的方案

第一步:更新useForm默认值

先把PlanProduct的默认值改成包含3个空对象的数组,让表单初始化时就对应好三个条目:

const {
  control,
  handleSubmit,
  formState: { errors },
} = useForm({
  defaultValues: {
    target: "",
    value: "",
    PlanProduct: [
      { createdBy: "", createdDate: "" },
      { createdBy: "", createdDate: "" },
      { createdBy: "", createdDate: "" },
    ],
  },
});

第二步:循环渲染数组项的输入字段

在表单里遍历固定长度的数组,为每个对象的createdBy和createdDate字段生成对应的Controller组件,注意字段名要使用数组索引的路径格式(比如PlanProduct[0].createdBy),React Hook Form会自动识别这种嵌套数组字段:

<form onSubmit={handleSubmit(onSubmit)}>
  {/* 原有的target字段 */}
  <Controller
    name="target"
    control={control}
    rules={{ required: "Cost is required" }}
    render={({ field: { onChange, onBlur, value, name, ref } }) => (
      <TextField
        variant="outlined"
        placeholder="Cost"
        inputRef={ref}
        sx={{
          backgroundColor: "ghostWhite",
          boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.1)",
          height: "35px",
          display: "flex",
          alignItems: "start",
          justifyContent: "center",
          borderRadius: "20px",
          flexGrow: "1",
          color: "black",
          marginTop: "18px",
          width: {
            xs: "100%",
            md: "auto",
          },
          "& .MuiOutlinedInput-root": {
            border: 0,
            boxShadow: "unset",
            width: "100%",
          },
          "& .MuiInputBase-colorPrimary": {
            border: 0,
            boxShadow: "unset",
          },
          "& .MuiOutlinedInput-input": {
            paddingTop: 0,
            paddingBottom: 0,
            display: "flex",
            alignItems: "center",
            border: "0",
          },
          "& .MuiOutlinedInput-notchedOutline": {
            border: 0,
          },
          "& .MuiInputLabel-root": {
            fontFamily: "Overpass",
          },
        }}
        value={value}
        onChange={onChange}
      />
    )}
  />

  {/* 新增的PlanProduct数组输入字段 */}
  {Array.from({ length: 3 }).map((_, index) => (
    <div key={index} style={{ marginTop: "18px", display: "flex", gap: "12px" }}>
      {/* createdBy输入框 */}
      <Controller
        name={`PlanProduct[${index}].createdBy`}
        control={control}
        rules={{ required: "Created By is required" }}
        render={({ field: { onChange, onBlur, value, ref } }) => (
          <TextField
            variant="outlined"
            placeholder={`Product ${index + 1} - Created By`}
            inputRef={ref}
            sx={{
              backgroundColor: "ghostWhite",
              boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.1)",
              height: "35px",
              borderRadius: "20px",
              flexGrow: 1,
              "& .MuiOutlinedInput-root": { border: 0, boxShadow: "unset" },
              "& .MuiOutlinedInput-notchedOutline": { border: 0 },
            }}
            value={value}
            onChange={onChange}
            error={!!errors?.PlanProduct?.[index]?.createdBy}
            helperText={errors?.PlanProduct?.[index]?.createdBy?.message}
          />
        )}
      />

      {/* createdDate输入框 */}
      <Controller
        name={`PlanProduct[${index}].createdDate`}
        control={control}
        rules={{ required: "Created Date is required" }}
        render={({ field: { onChange, onBlur, value, ref } }) => (
          <TextField
            variant="outlined"
            placeholder={`Product ${index + 1} - Created Date`}
            type="date"
            inputRef={ref}
            sx={{
              backgroundColor: "ghostWhite",
              boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.1)",
              height: "35px",
              borderRadius: "20px",
              flexGrow: 1,
              "& .MuiOutlinedInput-root": { border: 0, boxShadow: "unset" },
              "& .MuiOutlinedInput-notchedOutline": { border: 0 },
            }}
            value={value}
            onChange={onChange}
            error={!!errors?.PlanProduct?.[index]?.createdDate}
            helperText={errors?.PlanProduct?.[index]?.createdDate?.message}
            InputLabelProps={{ shrink: true }}
          />
        )}
      />
    </div>
  ))}

  {/* 提交按钮 */}
  <button type="submit" style={{ marginTop: "24px", padding: "8px 16px" }}>
    提交
  </button>
</form>

关键细节说明

  • 字段命名规则:React Hook Form通过PlanProduct[0].createdBy这种路径识别数组中的嵌套字段,自动将输入值绑定到对应数组项的属性上。
  • 错误处理:通过errors?.PlanProduct?.[index]?.createdBy链式访问,可针对每个数组项的字段单独显示错误提示。
  • 循环逻辑:用Array.from({ length: 3 })生成固定长度数组,确保渲染3组输入框。

提交后的数据结构

表单提交时,onSubmit函数接收的参数会自动生成符合要求的数组结构:

const onSubmit = (data) => {
  console.log(data.PlanProduct);
  // 输出示例:
  // [
  //   { createdBy: "用户输入值1", createdDate: "2024-05-20" },
  //   { createdBy: "用户输入值2", createdDate: "2024-05-21" },
  //   { createdBy: "用户输入值3", createdDate: "2024-05-22" },
  // ]
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:45:28