使用 useEffect 设置 React-Hook-Form 默认值
问题描述
在使用 React-Hook-Form 创建用户数据更新页面时,开发者希望通过 useEffect
钩子异步获取用户的当前数据,并将这些数据设置为表单的默认值。
常见的问题场景是:
- 从 API 获取数据后,表单字段没有显示预期的默认值
- 在
useEffect
中使用setValue
或设置defaultValue
但表单未更新 - 数据加载和表单初始化之间的时序问题
解决方案
根据 React-Hook-Form 的不同版本,有多种方法可以解决此问题。
方法一:使用 reset 方法(推荐)
这是最直接且兼容性最好的方法,适用于大多数版本:
jsx
import React, { useState, useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
const UpdateUserData = () => {
const [userData, setUserData] = useState(null);
const { handleSubmit, control, reset } = useForm({ mode: 'onBlur' });
useEffect(() => {
const fetchUserData = async () => {
const account = localStorage.getItem('account');
const response = await fetch(`${URL}/user/${account}`);
const data = await response.json();
setUserData(data);
// 关键步骤:使用 reset 设置表单值
reset(data);
};
fetchUserData();
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="name"
control={control}
render={({ field }) => <input {...field} type="text" />}
/>
<Controller
name="phone"
control={control}
render={({ field }) => <input {...field} type="text" />}
/>
</form>
);
};
TIP
使用 reset
方法可以一次性设置所有匹配字段的值,确保字段名称与 API 返回的数据键名一致。
方法二:使用 setValue 方法
对于需要逐个设置字段值的情况:
jsx
const { setValue } = useForm();
useEffect(() => {
if (userData) {
setValue('name', userData.name, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true
});
setValue('phone', userData.phone, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true
});
}
}, [userData, setValue]);
方法三:使用 values 属性(v7.0+)
React-Hook-Form v7 引入了 values
属性,可以响应式更新表单值:
jsx
function UpdateUserData() {
const [userData, setUserData] = useState({});
const { handleSubmit, control } = useForm({
defaultValues: {
name: "",
phone: ""
},
values: userData // 响应式更新表单值
});
useEffect(() => {
// 获取用户数据
const fetchData = async () => {
const account = localStorage.getItem('account');
const response = await fetch(`${URL}/user/${account}`);
const data = await response.json();
setUserData(data);
};
fetchData();
}, []);
}
方法四:异步默认值(v7.47+)
最新版本支持直接传递异步函数给 defaultValues
:
jsx
const {
formState: { isLoading },
handleSubmit,
control
} = useForm({
defaultValues: async () => {
const account = localStorage.getItem('account');
const response = await fetch(`${URL}/user/${account}`);
return response.json();
}
});
INFO
使用此方法时,isLoading
状态可以用于显示加载指示器。
方法五:父子组件模式
将数据获取和表单渲染分离到不同组件:
jsx
// 父组件
const UserDataPage = () => {
const [userData, setUserData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const account = localStorage.getItem('account');
const response = await fetch(`${URL}/user/${account}`);
const data = await response.json();
setUserData(data);
};
fetchData();
}, []);
return userData ? <UserForm defaultValues={userData} /> : <LoadingSpinner />;
};
// 子组件 - 表单
const UserForm = ({ defaultValues }) => {
const { handleSubmit, control } = useForm({
defaultValues
});
return (
// 表单内容
);
};
最佳实践建议
- 优先使用 reset 方法:这是最稳定和兼容的解决方案
- 确保字段名称匹配:API 返回的数据键名必须与表单字段的
name
属性一致 - 处理加载状态:在数据加载期间显示适当的加载指示器
- 考虑使用 TypeScript:定义接口确保类型安全
typescript
interface UserData {
name: string;
phone: string;
}
const { reset } = useForm<UserData>();
useEffect(() => {
// 获取数据并重置表单
reset(userData as UserData);
}, [userData]);
常见问题排查
- 表单未更新:检查
useEffect
依赖项是否正确设置 - 部分字段未填充:确认字段名称与数据键名完全匹配
- 性能问题:避免在每次渲染时重置表单,只在数据变化时重置
WARNING
避免在每次渲染时调用 reset
,这可能导致无限循环或性能问题。确保将其放在条件语句或适当的依赖项数组中。
通过选择适合您项目版本和需求的方法,可以有效地解决 React-Hook-Form 中动态设置默认值的问题。