react-hook-form中如何根据兄弟组件状态重置select选择器的值
问题核心原因
- 违反React Hook调用规则:你在
Controller的render回调的onChange属性中直接声明useEffect,Hook只能在React组件顶层调用,放在回调函数中既不会按预期触发,也会触发Hook规则校验错误。 - 状态多源不同步:你同时维护了react-hook-form(以下简称RHF)的表单字段状态,和自己声明的
selectedYear/selectedMonth本地状态,修改本地状态不会同步到RHF的表单数据源,所以绑定了field的选择组件不会响应变化。 - 组件属性书写错误:
SelectBirthMonth中Listbox的value属性误写为比较表达式,返回布尔值而非预期的月份选项对象。
解决方案
RHF修改绑定了Controller的字段值,直接使用useForm返回的setValue方法即可,不需要额外维护本地状态,以RHF的表单状态为唯一数据源即可。
第一步:父组件调整
首先从useForm中取出watch和setValue方法,直接监听年数字段变化:
// 父组件内的useForm调用 const { control, watch, setValue } = useForm({ defaultValues: { selectedBirthYear: years[0], selectedBirthMonth: months[0] } }); // 直接监听RHF的年数字段变化,不需要自己声明selectedYear state const selectedBirthYear = watch('selectedBirthYear');
简化两个Controller的写法,删除错误的内联useEffect:
{/* 年份选择Controller */} <Controller control={control} name="selectedBirthYear" render={({ field }) => ( <SelectBirthYear field={field} years={years} /> )} /> {/* 月份选择Controller */} <Controller control={control} name="selectedBirthMonth" render={({ field }) => ( <SelectBirthMonth field={field} startYear={startYear} selectedYear={selectedBirthYear} months={months} reducedMonths={reducedMonths} /> )} />
在父组件内添加监听逻辑,年份变化时直接用setValue修改月份字段的表单值:
useEffect(() => { if(Number(selectedBirthYear.name) === startYear){ const targetMonth = reducedMonths[reducedMonths.length - 1]; // 直接修改RHF的表单值,会自动同步到所有绑定了该字段的组件 setValue('selectedBirthMonth', targetMonth, { shouldValidate: true, // 需联动校验可开启 shouldDirty: true // 需标记字段为已修改可开启 }); } }, [selectedBirthYear, startYear, reducedMonths, setValue]);
第二步:调整SelectBirthMonth组件
删除组件内冗余的本地状态相关逻辑,修正Listbox的value属性错误:
export const SelectBirthMonth = ({ startYear, selectedYear, months, field, reducedMonths}) => { const { onChange, value } = field; return ( {/* 修正value属性的错误写法,直接取field.value即可,RHF会自动同步 */} <Listbox value={value} onChange={onChange}> {({ open }) => ( <> <Listbox.Label className="block text-sm font-medium text-gray-700">Geburtsmonat</Listbox.Label> <div className="mt-1 relative"> <Listbox.Button className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm"> <span className="block truncate">{value?.name}</span> <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> </span> </Listbox.Button> <Transition show={open} as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0" > <Listbox.Options static className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" > { Number(selectedYear.name) == startYear ? reducedMonths.map((month) => ( <ListboxOption key={month.id} value={month} date={month.name} > </ListboxOption> )) : months.map((month) => ( <ListboxOption key={month.id} value={month} date={month.name} > </ListboxOption> )) } </Listbox.Options> </Transition> </div> </> )} </Listbox> ) }
内容的提问来源于stack exchange,提问作者Katharina Schreiber
相关产品推荐
相关产品推荐

