Skip to content

使用 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 (
    // 表单内容
  );
};

最佳实践建议

  1. 优先使用 reset 方法:这是最稳定和兼容的解决方案
  2. 确保字段名称匹配:API 返回的数据键名必须与表单字段的 name 属性一致
  3. 处理加载状态:在数据加载期间显示适当的加载指示器
  4. 考虑使用 TypeScript:定义接口确保类型安全
typescript
interface UserData {
  name: string;
  phone: string;
}

const { reset } = useForm<UserData>();

useEffect(() => {
  // 获取数据并重置表单
  reset(userData as UserData);
}, [userData]);

常见问题排查

  1. 表单未更新:检查 useEffect 依赖项是否正确设置
  2. 部分字段未填充:确认字段名称与数据键名完全匹配
  3. 性能问题:避免在每次渲染时重置表单,只在数据变化时重置

WARNING

避免在每次渲染时调用 reset,这可能导致无限循环或性能问题。确保将其放在条件语句或适当的依赖项数组中。

通过选择适合您项目版本和需求的方法,可以有效地解决 React-Hook-Form 中动态设置默认值的问题。