React Hook Form自动追加动态表单重置时重复添加输入框问题
问题原因
调用reset方法时,useWatch返回的数组会触发多次状态更新:
- 第一次是
reset将表单值设置为[{text: "X"}],此时最后一项非空,触发append添加空项; - 由于
react-hook-form内部状态同步机制,useWatch的监听会再次触发(useWatch与useFieldArray的fields存在状态不同步的可能),导致append被重复调用,最终末尾出现两个空输入框。
另外,useWatch是独立于useFieldArray的监听机制,相比直接使用useFieldArray返回的fields,更容易出现状态不同步的问题。
解决方案
方案1:改用useFieldArray的fields监听(推荐)
直接使用useFieldArray返回的fields替代useWatch,保证状态与useFieldArray完全同步,避免重复触发:
import { useEffect } from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; interface FormValues { items: { text: string; }[]; } export default function DynamicForm() { const { control, reset } = useForm<FormValues>({ defaultValues: { items: [{ text: "" }], }, }); const { fields, append } = useFieldArray({ control, name: "items", }); // 直接监听fields而非useWatch返回的数组 useEffect(() => { if (fields.length === 0) return; const lastItem = fields[fields.length - 1]; if (lastItem.text.trim() !== "") { append({ text: "" }, { shouldFocus: false }); } }, [fields, append]); const handleInsertData = () => { reset({ items: [{ text: "X" }], }); }; return ( <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md"> <button onClick={handleInsertData}>Insert Data</button> <h2 className="text-xl font-bold mb-4">Dynamic Form</h2> <form> <div className="space-y-4"> {fields.map((field, index) => ( <div key={field.id} className="flex items-start space-x-2"> <div className="flex-grow"> <Controller control={control} name={`items.${index}.text`} render={({ field }) => ( <textarea {...field} className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter text..." /> )} /> </div> </div> ))} </div> </form> </div> ); }
方案2:添加防重复触发标记
使用ref跟踪是否正在执行append操作,避免同一周期内重复触发:
import { useEffect, useRef } from "react"; import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"; interface FormValues { items: { text: string; }[]; } export default function DynamicForm() { const { control, reset } = useForm<FormValues>({ defaultValues: { items: [{ text: "" }], }, }); const { fields, append } = useFieldArray({ control, name: "items", }); const watchItems = useWatch({ control, name: "items", }); // 用ref标记是否正在追加,避免重复触发 const isAppendingRef = useRef(false); useEffect(() => { if (watchItems.length === 0 || isAppendingRef.current) return; const lastItem = watchItems[watchItems.length - 1]; if (lastItem.text.trim() !== "") { isAppendingRef.current = true; append({ text: "" }, { shouldFocus: false }); // 在下一周期重置标记 setTimeout(() => { isAppendingRef.current = false; }, 0); } }, [watchItems, append]); const handleInsertData = () => { reset({ items: [{ text: "X" }], }); }; return ( <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md"> <button onClick={handleInsertData}>Insert Data</button> <h2 className="text-xl font-bold mb-4">Dynamic Form</h2> <form> <div className="space-y-4"> {fields.map((field, index) => ( <div key={field.id} className="flex items-start space-x-2"> <div className="flex-grow"> <Controller control={control} name={`items.${index}.text`} render={({ field }) => ( <textarea {...field} className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter text..." /> )} /> </div> </div> ))} </div> </form> </div> ); }
方案3:重置时暂时禁用自动追加逻辑
通过状态标记重置过程,重置完成后再恢复自动追加:
import { useEffect, useState } from "react"; import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"; interface FormValues { items: { text: string; }[]; } export default function DynamicForm() { const { control, reset } = useForm<FormValues>({ defaultValues: { items: [{ text: "" }], }, }); const { fields, append } = useFieldArray({ control, name: "items", }); const watchItems = useWatch({ control, name: "items", }); const [isResetting, setIsResetting] = useState(false); useEffect(() => { if (isResetting || watchItems.length === 0) return; const lastItem = watchItems[watchItems.length - 1]; if (lastItem.text.trim() !== "") { append({ text: "" }, { shouldFocus: false }); } }, [watchItems, append, isResetting]); const handleInsertData = () => { setIsResetting(true); reset({ items: [{ text: "X" }], }); // 重置完成后恢复自动追加 setTimeout(() => { setIsResetting(false); }, 0); }; return ( <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md"> <button onClick={handleInsertData}>Insert Data</button> <h2 className="text-xl font-bold mb-4">Dynamic Form</h2> <form> <div className="space-y-4"> {fields.map((field, index) => ( <div key={field.id} className="flex items-start space-x-2"> <div className="flex-grow"> <Controller control={control} name={`items.${index}.text`} render={({ field }) => ( <textarea {...field} className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter text..." /> )} /> </div> </div> ))} </div> </form> </div> ); }
最佳实践
- 使用
useFieldArray时,优先用其返回的fields监听数组状态,避免useWatch带来的状态不同步问题; - 自动追加逻辑需添加防重复触发判断,避免因多次状态更新导致重复操作;
- 执行
reset等批量修改操作时,可暂时禁用自动逻辑,避免状态冲突。
内容的提问来源于stack exchange,提问作者Yannic
相关产品推荐
相关产品推荐

