如何在React Hook Form中处理动态增减的组件?
用react-hook-form处理动态增删的表单组件列表
核心方案:使用useFieldArray钩子
react-hook-form专门提供了useFieldArray来处理动态数组类型的表单字段,它能自动同步列表增删操作与表单状态,无需手动维护组件列表和表单数据的一致性。
步骤1:改造父组件
- 引入
useForm和useFieldArray钩子 - 初始化表单时设置数组字段的默认值
- 用
useFieldArray返回的fields、append、remove替代手动维护的组件列表状态 - 遍历
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
- 接收父组件传递的
register、index、增删方法 - 给每个输入框的
name设置为数组名[索引].字段名的格式,让react-hook-form识别为数组元素的属性 - 绑定增删按钮的点击事件到对应方法
改造后的子组件代码:
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
相关产品推荐
相关产品推荐

