You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在React Context中使用react-hook-form出现状态异常的问题咨询

在React Context中使用react-hook-form出现状态异常的问题咨询

嘿,我看到你把react-hook-form的useForm放在Context Provider里使用时遇到了几个棘手的问题:提交错误不触发、watch看不到输入变化、isDirty这类状态始终停留在初始值,但把useForm直接移到表单组件里就一切正常。我来帮你拆解下原因和解决办法~

问题核心原因

react-hook-form的useForm返回的实例是和组件渲染周期深度绑定的,把它放在Context中时,容易触发以下几个问题:

  • Provider渲染导致form实例重置:如果Provider因为props变化(比如children、defaultValues)重新渲染,会重新调用useForm生成全新的实例,之前的状态直接丢失,组件拿到的是新实例,自然看不到状态变化。
  • 状态更新传递不及时:Context的状态更新需要Provider重新渲染才能传递给消费组件,如果Provider没有正确触发渲染,组件就无法及时获取到form的最新状态。
  • 自定义组件绑定有误:看你代码里的PhoneInput组件,onChange的处理方式没有正确把完整值传递给react-hook-form的field.onChange,导致form实例捕获不到输入变化,进而isDirty这类状态不会更新。

具体修复步骤

1. 修正Provider里的defaultValues使用

你的Provider定义了defaultValues参数,但实际用的是未定义的formDefault(应该是笔误),而且没确保初始值的稳定性,这会导致不必要的Provider渲染和form实例重置。修改如下:

// 先定义默认初始值
const defaultFormValues: BuyerForm = {
  id: undefined,
  companyName: '',
  companyType: CompanyTypeEnum.XXX, // 替换为你的默认枚举值
  companyPhone: { phone: '', country: {} },
  streetLine1: '',
  streetLine2: '',
  city: '',
  stateOrRegion: '',
  postalCode: '',
  country: {},
  website: '',
  defaultCurrency: '',
  contactName: '',
  contactPosition: '',
  contactPhone: { phone: '', country: {} },
  contactEmail: '',
};

export const BuyerFormProvider = ({
  children,
  defaultValues = defaultFormValues,
}: {
  children: ReactNode;
  defaultValues?: BuyerForm;
}) => {
  // 用useMemo确保defaultValues稳定,避免不必要的useForm重新初始化
  const stableDefaultValues = React.useMemo(() => defaultValues, [defaultValues]);
  
  const form = useForm<BuyerForm>({
    resolver: zodResolver(BuyerFormSchema),
    mode: 'onSubmit',
    defaultValues: stableDefaultValues,
  });

  return <BuyerFormContext.Provider value={form}>{children}</BuyerFormContext.Provider>;
};

2. 修复自定义组件的onChange绑定

看你PhoneInput的onChange处理,没必要解构后再组装,直接把完整的value对象传给field.onChange即可,这样form实例能正确捕获值的变化;另外别忘了加FormMessage组件,否则提交错误不会显示:

<FormField
  control={form.control}
  name='companyPhone'
  render={({ field }) => (
    <FormItem className='flex-1'>
      <FormLabel>Company Phone</FormLabel>
      <FormControl>
        <PhoneInput
          {...field}
          onChange={(value) => field.onChange(value)} // 直接传递完整value对象
          placeholder='Enter your number'
          defaultCountry={field.value.country?.alpha2}
          className='h-10'
        />
      </FormControl>
      <FormDescription>Include country code (e.g. +44)</FormDescription>
      <FormMessage /> {/* 必须添加这个组件才会显示错误提示 */}
    </FormItem>
  )}
/>

3. 避免Provider不必要的渲染

可以用React.memo包裹Provider组件,防止无关props变化导致Provider重新渲染:

export const BuyerFormProvider = React.memo(({
  children,
  defaultValues = defaultFormValues,
}: {
  children: ReactNode;
  defaultValues?: BuyerForm;
}) => {
  // 内部代码不变
});

4. 正确使用watch钩子

watch是react-hook-form的钩子,必须在消费组件内部调用,不能在Context里调用后传递给组件。比如在表单组件里这样用:

const BuyerForm = () => {
  const form = useBuyerForm();
  const { watch } = form;
  const companyName = watch('companyName'); // 在组件内部调用watch
  // ...
};

为什么放在组件里就正常?

当useForm直接在表单组件里调用时,form实例和组件的渲染周期完全绑定,组件的每一次渲染都会正确反映form的状态变化,而且不会因为外部Provider的渲染而重置实例,所有状态更新都能及时传递到组件内部的表单字段,所以一切正常。

备注:内容来源于stack exchange,提问作者anubhav adarsh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:19:38