使用react-hook-form的watch方法导致无限循环问题求助
React Hook Form 结合 useState 引发无限循环问题解决
问题场景
在Form组件中使用react-hook-form,需实时更新父组件App的状态,且Form组件无提交按钮,因此采用watch方法监听表单数据。App组件通过useState保存数据,尝试通过回调prop更新App数据时出现无限循环。
App组件代码
import { FC, useState } from 'react'; import { Form } from './Form'; export const App: FC<{ name: string }> = ({ name }) => { const [data, setData] = useState({ name: 'David' }); return ( <div> <h1>Hello {data.name}!</h1> <Form setData={setData} /> </div> ); };
Form组件代码
import React, { useEffect } from 'react'; import { useForm } from 'react-hook-form'; export const Form = ({ setData }) => { const { register, watch } = useForm(); const formData = watch(); useEffect(() => { setData(formData); }, [formData]); return ( <div> <h2>Form comp</h2> <form> <input name="name" {...register('name')} /> </form> </div> ); };
问题根源
无限循环的核心逻辑链:
watch()在每次组件渲染时都会返回新的对象引用,即便表单数据未发生变化- 新对象触发
useEffect的依赖项变更,调用setData更新App的state - App更新state会触发自身及子组件Form重新渲染,
watch()再次返回新对象,循环往复
解决方案
方案1:精准监听单个字段
若只需监听特定字段(如示例中的name),避免监听整个表单对象,直接指定字段监听:
import React, { useEffect } from 'react'; import { useForm } from 'react-hook-form'; export const Form = ({ setData }) => { const { register, watch } = useForm(); // 仅监听name字段,返回值为字符串而非对象,引用稳定 const name = watch('name'); useEffect(() => { setData({ name }); }, [name, setData]); return ( <div> <h2>Form comp</h2> <form> <input name="name" {...register('name')} /> </form> </div> ); };
方案2:使用useWatch保证引用稳定性
react-hook-form提供useWatch钩子,它会返回稳定的引用,仅当监听的字段实际变化时才更新:
import React, { useEffect } from 'react'; import { useForm, useWatch } from 'react-hook-form'; export const Form = ({ setData }) => { const { register, control } = useForm(); // 使用useWatch替代watch,传入control控制引用稳定性 const formData = useWatch({ control }); useEffect(() => { setData(formData); }, [formData, setData]); return ( <div> <h2>Form comp</h2> <form> <input name="name" {...register('name')} /> </form> </div> ); };
方案3:手动添加数据浅比较
若必须监听整个表单,可在useEffect中手动对比前后数据,仅当数据真的变化时才更新父组件state:
import React, { useEffect, useRef } from 'react'; import { useForm } from 'react-hook-form'; export const Form = ({ setData }) => { const { register, watch } = useForm(); const formData = watch(); // 用ref保存上一次的表单数据 const prevDataRef = useRef(formData); useEffect(() => { // 浅比较对象内容是否变化(简单场景可用JSON.stringify,复杂场景建议用lodash.isEqual) if (JSON.stringify(prevDataRef.current) !== JSON.stringify(formData)) { setData(formData); prevDataRef.current = formData; } }, [formData, setData]); return ( <div> <h2>Form comp</h2> <form> <input name="name" {...register('name')} /> </form> </div> ); };
内容的提问来源于stack exchange,提问作者Shlomy Z
相关产品推荐
相关产品推荐

