react-hook-form的watch方法在select元素渲染后获取初始值异常问题
问题根因
这个问题的本质是首次渲染时select组件注册到react-hook-form时,下拉选项还没从服务端拉取回来,此时select没有可选中的<option>,react-hook-form会将该字段的初始值设为空字符串。后续下拉选项填充完成后,浏览器会默认选中第一个选项,但这个DOM层面的默认选中行为没有触发表单的change事件,因此react-hook-form无法感知到值的变化,watch返回的值还是之前的空字符串。
可行解决方案
方案1:下拉选项加载完成后手动赋值
使用react-hook-form提供的setValue方法,在下拉选项请求完成后主动给字段赋初始值:
import React, { useState, useEffect } from 'react'; import { useForm } from "react-hook-form"; function App() { const { register, watch, setValue } = useForm(); const watchFoo = watch("foo"); const [foos, setFoos] = useState([]); useEffect(() => { const newFoos = [{ id: 1, name: "one" }, { id: 2, name: "two" }]; setFoos(newFoos); // 选项加载完成后手动设置初始值 setValue('foo', newFoos[0].id); }, []); return ( <form> <select {...register('foo')}> {foos.map((foo, i) => (<option key={`option_${i}`} value={foo.id}>{foo.name}</option>))} </select> <p>foo is {watchFoo}</p> </form> ); } export default App;
这个方案是最常用的,逻辑简单清晰,不需要修改组件渲染逻辑。
方案2:延迟渲染select组件
等下拉选项加载完成后再渲染select,保证注册到react-hook-form时已经有可用的选项:
import React, { useState, useEffect } from 'react'; import { useForm } from "react-hook-form"; function App() { const { register, watch } = useForm(); const watchFoo = watch("foo"); const [foos, setFoos] = useState([]); useEffect(() => { setFoos([{ id: 1, name: "one" }, { id: 2, name: "two" }]); }, []); return ( <form> {/* 选项加载完成后才渲染select */} {foos.length > 0 && ( <select {...register('foo')}> {foos.map((foo, i) => (<option key={`option_${i}`} value={foo.id}>{foo.name}</option>))} </select> )} <p>foo is {watchFoo}</p> </form> ); } export default App;
如果可以接受加载选项期间不展示下拉框,这个方案也非常简洁。
方案3:使用Controller实现受控组件
如果是复杂的表单场景,也可以用react-hook-form提供的Controller组件将下拉框改成受控组件,直接控制值的变化:
import React, { useState, useEffect } from 'react'; import { useForm, Controller } from "react-hook-form"; function App() { const { control, watch } = useForm(); const watchFoo = watch("foo"); const [foos, setFoos] = useState([]); useEffect(() => { setFoos([{ id: 1, name: "one" }, { id: 2, name: "two" }]); }, []); return ( <form> <Controller name="foo" control={control} defaultValue={foos[0]?.id} render={({ field }) => ( <select {...field}> {foos.map((foo, i) => (<option key={`option_${i}`} value={foo.id}>{foo.name}</option>))} </select> )} /> <p>foo is {watchFoo}</p> </form> ); } export default App;
这个方案适合和其他受控组件配合使用的场景,值的更新逻辑完全由react-hook-form管控,不会出现同步问题。
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

