React-Hook-Form中getValues与setValue无法获取完整当前值问题
React-Hook-Form动态数组字段丢失title值问题解决
问题概述
使用React-Hook-Form处理依赖多源动态数据的复杂表单时,遇到以下问题:
- 表单包含动态数组
goals,数组项通过开关控制是否显示备注字段。 - 仅当前会话中切换过开关的
goals项,其title字段能在getValues()结果中保留;历史已标记为true的项(已包含在defaultValues中)的title会丢失,导致提交前传递给签名组件的数据不完整。 - 不能要求用户每次提交前重新切换历史项,需要保持良好的UX并复用签名组件。
原因分析
- 表单状态跟踪逻辑缺失:React-Hook-Form仅跟踪与表单控件绑定的字段,
title未绑定到任何控件,仅在开关切换时通过setValue手动添加到表单状态,历史项未触发此逻辑,因此未被跟踪。 - defaultValues未完全生效:虽然
defaultValues包含完整的goals数组,但未显式将title同步到表单状态,导致未绑定控件的字段被忽略。
解决方案
方案:初始化时同步所有历史项的title到表单状态
在父组件初始化完成后,遍历goals数组,对每个已标记为true的项,调用setValue将title写入表单状态,确保所有历史项的title被表单跟踪。
修改后的代码示例
父组件(EditVisit)
const EditVisit = () => { const hasVisit = useSelector(state => state.visit); const goals = hasVisit.goals; const { register, handleSubmit, control, setValue, watch, getValues, formState: { errors, dirtyFields } } = useForm({ defaultValues: { visitStart: hasVisit.visitStart, visitEnd: hasVisit.visitEnd, location: hasVisit.location, goals: hasVisit.goals, status: hasVisit.status, cancelNote: hasVisit.cancelNote }, shouldUnregister: true }); // 初始化同步已标记项的title到表单状态 useEffect(() => { if (goals?.length) { goals.forEach((goal, index) => { if (goal.marked) { setValue(`goals.${index}.title`, goal.title); } }); } }, [goals, setValue]); const onSubmit = (data) => { console.log("提交数据:", data); // 传递data给签名组件并完成提交 }; return ( <form onSubmit={handleSubmit(onSubmit)}> {/* 其他表单字段 */} {goals.length >= 1 ? ( goals.map((goal, index) => ( <GoalInput key={index} goal={goal} index={index} control={control} errors={errors} /> )) ) : ( <Typography>No service plan with goals for this date. Goals must be set in the plan to reflect here.</Typography> )} {/* 签名组件和提交按钮 */} </form> ); };
GoalInput组件
function GoalInput({ goal, index, control, errors }) { // 直接用goal.marked初始化toggle状态 const [toggle, setToggle] = useState(goal.marked === true); // 监听goal.marked的外部变化,同步toggle状态 useEffect(() => { setToggle(goal.marked === true); }, [goal.marked]); return ( <React.Fragment key={index}> <Controller control={control} type="checkbox" name={`goals.${index}.marked`} defaultValue={goal?.marked} render={({ field }) => ( <Switch checked={toggle} onChange={(e) => { field.onChange(e); setToggle(!toggle); }} value={field.value} /> )} /> <label>{goal?.title}</label> <br /> {toggle ? ( <> <Controller control={control} name={`goals.${index}.note`} id={`goals.${index}.note`} defaultValue={goal?.note} render={({ field }) => ( <TextField index={index} error={!!errors?.goals?.[index]?.note} value={field.value} onChange={(e) => field.onChange(e)} label="Progress Note" /> )} /> <br /> </> ) : null} </React.Fragment> ); }
效果验证
修改后,getValues()返回的goals数组将包含所有已标记项的title字段,无论是否在当前会话中切换过开关,确保传递给签名组件的数据完整。
内容的提问来源于stack exchange,提问作者Jowz
相关产品推荐
相关产品推荐

