如何在React Hook Form中实现动态添加输入元素的需求
React Hook Form 自定义动态字段实现方案
核心用到React Hook Form的原生API即可实现需求,不需要额外安装第三方依赖,具体实现逻辑和示例如下:
核心实现思路
- 初始化空白表单,不预设任何默认字段
- 用本地状态存储用户新增的自定义字段名,用于动态渲染输入框
- 新增字段时调用
setValue给表单动态注册字段并赋值初始值 - 动态渲染输入框时通过
register绑定字段,自动同步表单的change、dirty状态
完整可运行示例
import { useForm } from "react-hook-form"; import { useState } from "react"; // 动态表单值类型,字段名不固定 type FormValues = Record<string, any>; export default function DynamicCustomForm() { const { register, handleSubmit, setValue, getValues, unset, formState: { dirtyFields, isDirty } } = useForm<FormValues>({ defaultValues: {} // 初始空白表单 }); // 存储已新增的自定义字段名,用于动态渲染 const [customFields, setCustomFields] = useState<string[]>([]); // 外部新增字段的输入框状态 const [newFieldConfig, setNewFieldConfig] = useState({ fieldName: "", initValue: "" }); // 处理新增字段提交逻辑 const handleAddCustomField = () => { const { fieldName, initValue } = newFieldConfig; // 字段名不能为空,且避免重复添加同名字段 if (!fieldName.trim() || getValues(fieldName) !== undefined) return; // 给表单动态注册新字段并赋值初始值 setValue(fieldName, initValue, { shouldValidate: false, shouldDirty: false // 初始赋值默认不算用户修改,如需标记为修改可改为true }); // 更新动态渲染列表 setCustomFields(prev => [...prev, fieldName]); // 清空新增输入框 setNewFieldConfig({ fieldName: "", initValue: "" }); }; // 处理删除动态字段逻辑(可选扩展) const handleDeleteField = (fieldName: string) => { setCustomFields(prev => prev.filter(item => item !== fieldName)); unset(fieldName); // 清理表单中对应字段的数据和状态 }; // 表单提交逻辑 const onFormSubmit = (data: FormValues) => { console.log("表单全量数据:", data); console.log("用户修改过的字段:", dirtyFields); console.log("表单是否被修改:", isDirty); }; return ( <div style={{ padding: "20px", maxWidth: "600px" }}> {/* 外部新增字段区域 */} <div style={{ marginBottom: "24px", padding: "16px", border: "1px solid #e5e7eb" }}> <h4>新增自定义字段</h4> <input placeholder="请输入字段名" value={newFieldConfig.fieldName} onChange={(e) => setNewFieldConfig(prev => ({ ...prev, fieldName: e.target.value }))} style={{ marginRight: "12px", padding: "4px 8px" }} /> <input placeholder="请输入初始值" value={newFieldConfig.initValue} onChange={(e) => setNewFieldConfig(prev => ({ ...prev, initValue: e.target.value }))} style={{ marginRight: "12px", padding: "4px 8px" }} /> <button type="button" onClick={handleAddCustomField} style={{ padding: "4px 12px" }}> 添加到表单 </button> </div> {/* 表单区域 */} <form onSubmit={handleSubmit(onFormSubmit)}> <h4>表单内容</h4> {/* 动态渲染已新增的自定义字段 */} {customFields.length === 0 ? ( <p>暂无自定义字段,请先从上方添加</p> ) : ( customFields.map(fieldName => ( <div key={fieldName} style={{ marginBottom: "12px", display: "flex", gap: "12px", alignItems: "center" }}> <label>{fieldName}:</label> <input {...register(fieldName)} style={{ flex: 1, padding: "4px 8px" }} /> <button type="button" onClick={() => handleDeleteField(fieldName)} style={{ color: "red" }}> 删除 </button> </div> )) )} {customFields.length > 0 && ( <button type="submit" style={{ marginTop: "16px", padding: "6px 16px" }}> 提交表单 </button> )} </form> </div> ); }
关键说明
- 状态同步:通过
register绑定的动态输入框,修改时会自动触发React Hook Form的状态更新,dirtyFields、isDirty、touched等内置状态都会自动同步,不需要额外手动处理 - 校验扩展:如果需要给动态字段增加校验规则,修改
register的入参即可,比如register(fieldName, { required: "该字段为必填项", minLength: { value: 2, message: "最少输入2个字符" } }) - 状态标记:调用
setValue赋值初始值时,可以通过第三个参数调整状态标记,比如设置shouldDirty: true可以让初始赋值就标记为用户修改状态
内容的提问来源于stack exchange,提问作者vipul shinde
相关产品推荐
相关产品推荐

