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

如何使用react final form实现重复表单动态添加与统一提交

可行性结论

完全可行,React Final Form 原生支持动态数组类字段,不需要额外引入适配依赖,就能实现点击按钮重复渲染表单、所有数据统一提交的需求。

具体实现方案

核心思路是把所有重复的产品表单挂载到同一个数组类型的根字段下,每点击一次新增按钮就往数组里追加一条空的单表单初始值,遍历数组渲染对应结构的表单块即可,提交时整个数组会随表单数据一同输出,不需要额外做数据拼接。

  • 初始化表单默认值:设置表单initialValues时,给重复表单预留数组字段,比如命名为productList,默认放1个空对象[{}],对应页面首次加载时默认渲染1组空白产品表单。
  • 抽离单组表单的可复用组件:把单份产品表单的所有字段、校验逻辑抽成独立组件,组件接收当前项的数组下标index作为入参,内部所有字段的name属性都拼接数组路径前缀,比如原产品名字段productName要改写为`productList[${index}].productName`,产品描述字段对应改为`productList[${index}].desc`,保证每个重复表单的字段路径唯一,不会出现值互相覆盖的问题。原有单表单的校验规则可以直接保留,React Final Form会自动识别数组路径下的字段状态,不会出现校验串扰。
  • 实现新增/删除逻辑:从表单渲染参数里拿到values(当前表单所有值)和form.change方法,点击Add another item按钮时,取当前values.productList的副本,往末尾追加一个和单表单初始结构一致的空对象,调用change('productList', 新数组)更新表单值,页面会自动渲染出新的一组表单。如果需要加删除单组表单的功能,同理取数组副本后过滤掉对应下标的项,再调用change更新值即可。
  • 提交处理:触发表单提交时,回调拿到的values参数里会直接包含完整的productList数组,数组的每一项对应一组产品表单的填写内容,直接把整个values传给提交接口即可。

踩坑提示:不要给遍历生成的表单块用随机数当key,用数组下标即可,否则会出现输入时值错位、组件状态丢失的问题。

核心实现代码参考:

import { Form, Field } from 'react-final-form'

// 单组产品表单组件
const ProductFormBlock = ({ index, onRemove }) => {
  return (
    <div className="p-4 border rounded mb-3">
      <div className="mb-2">
        <label className="block text-sm mb-1">产品名称</label>
        <Field
          name={`productList[${index}].name`}
          validate={val => val ? undefined : '产品名称不能为空'}
        >
          {({ input, meta }) => (
            <>
              <input {...input} className="border rounded px-2 py-1 w-full" />
              {meta.touched && meta.error && <span className="text-red-500 text-xs">{meta.error}</span>}
            </>
          )}
        </Field>
      </div>
      <div className="mb-2">
        <label className="block text-sm mb-1">产品价格</label>
        <Field
          name={`productList[${index}].price`}
          validate={val => !isNaN(Number(val)) && Number(val) >=0 ? undefined : '请输入合法的价格数值'}
        >
          {({ input, meta }) => (
            <>
              <input {...input} type="number" className="border rounded px-2 py-1 w-full" />
              {meta.touched && meta.error && <span className="text-red-500 text-xs">{meta.error}</span>}
            </>
          )}
        </Field>
      </div>
      {index > 0 && (
        <button type="button" onClick={onRemove} className="text-red-500 text-sm">
          删除该产品
        </button>
      )}
    </div>
  )
}

// 页面组件
const ProductBatchAddPage = () => {
  const handleSubmit = (allValues) => {
    // allValues.productList 就是所有填写完成的产品数据,直接提交即可
    console.log('全量提交数据:', allValues)
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-xl font-bold mb-4">批量新增产品</h1>
      <Form
        initialValues={{ productList: [{}] }}
        onSubmit={handleSubmit}
        render={({ handleSubmit, values, form: { change } }) => (
          <form onSubmit={handleSubmit}>
            {values.productList.map((_, index) => (
              <ProductFormBlock
                key={index}
                index={index}
                onRemove={() => {
                  const updatedList = values.productList.filter((_, i) => i !== index)
                  change('productList', updatedList)
                }}
              />
            ))}

            <button
              type="button"
              className="border rounded px-4 py-2 mr-3"
              onClick={() => {
                const updatedList = [...values.productList, {}]
                change('productList', updatedList)
              }}
            >
              Add another item
            </button>
            <button type="submit" className="bg-blue-500 text-white rounded px-4 py-2">
              提交所有产品
            </button>
          </form>
        )}
      />
    </div>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:54:25