如何使用useEffect()更改React-Hook-Form中的defaultValue?

136
我正在使用React-Hook-Form创建页面,用于更新用户个人数据。一旦页面加载完成,我使用useEffect来获取用户当前的个人数据,并将其设置为表单的默认值。
我将获取到的值放入<Controller />defaultValue中。 然而,在文本框中并没有显示默认值。 以下是我的代码:

import React, {useState, useEffect, useCallback} from 'react';
import { useForm, Controller } from 'react-hook-form'
import { URL } from '../constants';

const UpdateUserData = props => {
    const [userData, setUserData] = useState(null);
    const { handleSubmit, control} = useForm({mode: 'onBlur'});

    const fetchUserData = useCallback(async account => {
        const userData = await fetch(`${URL}/user/${account}`)
                            .then(res=> res.json());
        console.log(userData);
        setUserData(userData);
    }, []);

    useEffect(() => {
        const account = localStorage.getItem('account');
        fetchUserData(account);
    }, [fetchUserData])

    const onSubmit = async (data) => {
        // TODO
    }

    return (
        <div>
            <form onSubmit={handleSubmit(onSubmit)}>
                <div>
                    <label>User Name:</label>
                    <Controller
                        as={<input type='text' />}
                        control={control}
                        defaultValue={userData ? userData.name : ''}
                        name='name'
                    />
                </div>
                
                <div>
                    <label>Phone:</label>
                    <Controller
                        as={<input type='text' />}
                        control={control}
                        defaultValue={userData ? userData.phone : ''}
                        name='phone'
                    />
                </div>
                <button>Submit</button>
            </form>
        </div>
    );
}

export default UpdateUserData;
调用的API运行良好,实际上将值设置为userData状态。
{
  name: "John",
  phone: "02-98541566"
  ...
}

我也尝试了在useEffect()中使用模拟数据来setUserData,但它也不起作用。上面的代码有什么问题吗?


抱歉,我没有明白你想要实现什么,请你能详细解释一下吗? - adel
12个回答

156
@tam的答案只解决了使其与6.8.3版本兼容的一半问题。
你需要提供默认值,并使用useEffect进行重置。如果你有一个需要重新加载另一个实体的表单,这个特定的区别是必需的。我在CodeSanbox这里有一个完整的示例。
简而言之,你需要在useForm中定义你的defaultValues。
const { register, reset, handleSubmit } = useForm({
    defaultValues: useMemo(() => {
        return props.user;
    }, [props])
});

然后你需要倾听潜在的变化。
useEffect(() => {
    reset(props.user);
}, [props.user]);

在Code Sandbox的示例中,可以在两个用户之间进行切换,并且表单会更改其值。

8
这绝对是这里最简洁明了的答案之一。 - Dev
1
@ Patrick,这个可以。只是有一个疑惑,当我没有将其包装在useMemo中时,也有效果,我们使用useMemo的原因是它不会在下一次渲染之前重新计算,除非任何依赖项发生了变化。使用useMemo和直接传递对象使用useMemo有什么区别?由于useEffect已经存在,是否可以不使用useMemo来完成任务?如果我错了,请纠正我-React新手 :) - dev
9
好的,我会尽力进行翻译。对于你的问题,“为什么你在这里使用useMemo?”,useMemo是React中的一个钩子函数,用于优化组件性能。它可以缓存函数的计算结果,并在依赖项没有变化时返回缓存的结果,从而避免不必要的重新计算。因此,使用useMemo可以提高组件的渲染效率。 - Jaeeun Lee
这是正确的。 - Bon Andre Opina
嗨@Patricks,请问你可以给我这个问题的答案吗?https://stackoverflow.com/questions/76859696/react-hook-form-dynamic-add-form-is-not-working-properly - Rohit Azad Malik
显示剩余2条评论

86
你可以使用setValue (https://react-hook-form.com/api/useform/setvalue)。
从useForm中导入它:
const { handleSubmit, control, setValue} = useForm({ mode: 'onBlur' });

在接收到用户数据后,调用它:

useEffect(() => {
    if (userData) {
        setValue([
            { name: userData.name }, 
            { phone: userData.phone }
        ]);
    }
}, [userData]);

您可以从表单中删除默认值。

编辑:如果此方法不起作用,请参见下面的替代答案。


1
寻找一次性为所有FormValues设置setValue的TypeScript版本。 - Prasad Phule
如何使用setValue方法来操作复选框和单选按钮? - Mark Redman
1
对我没用,出现了路径分割错误。下面的答案倒是有效的。 - Krishna Acharya
1
我猜想自此答案起 setValue 的定义已经有所更改。现在它接受一个名称和值,例如 setValue(fieldName, fieldValue)。使用这种方法设置单个字段是有效的;但不幸的是并不是我们的目标。 - emragins
@emragins 你可以使用替换或更新的方法来处理字段数组,但是它们会导致组件对目标字段数组进行卸载和重新挂载。 - undefined
显示剩余3条评论

54

setValue 对我不起作用。你可以使用 reset 方法代替:

重置整个表单状态或部分表单状态。

这是可行的代码:

 /* registered address */
const [registeredAddresses, setRegisteredAddresses] = useState([]);

const { register, errors, handleSubmit, reset } = useForm <FormProps> ({
    validationSchema: LoginSchema,
});

/**
 * get addresses data
 */
const getRegisteredAddresses = async () => {
    try {
        const addresses = await AddressService.getAllAddress();
        setRegisteredAddresses(addresses);
        setDataFetching(false);
    } catch (error) {
        setDataFetching(false);
    }
};

useEffect(() => {
    getRegisteredAddresses();
}, []);

useEffect(() => {
    if (registeredAddresses) {
        reset({
            addressName: registeredAddresses[0].name,
            tel: registeredAddresses[0].contactNumber
        });
    }
}, [registeredAddresses]); 

谢谢,reset({ ...reduxState }) 是我一直在寻找的,以保持formState和reduxState之间的一致性。 - deadbyte

34

我发现另一种简单的方式,我使用了useForm中的reset API。

 const { handleSubmit, register, reset } = useForm({ resolver });

在调用API并获取响应数据后,您可以使用新的apiData调用reset函数,确保apiData中的键与输入键(name属性)相同:

 useEffect(() => {
    reset(apiData);
  }, [apiData]);

表单的默认值被缓存,因此一旦从API获取数据,我们会用新数据重置表单状态。


1
你不需要使用 useEffect,当你直接在 .then 部分收到 apiData 时,只需执行此操作即可。 - RollingInTheDeep
1
这也可以,它取决于实现方式,通常API调用和数据处理是在不同的文件中完成的。@RollingInTheDeep - Harish Kulkarni

19

@tommcandrew的setValue参数格式对我没有起作用。

这种格式可以:

useEffect(() => {
  const object = localStorage.getItem('object');
  setValue("name", object.name);
}, [])

这对我来说在React Native和控制器包装器中运作良好。 - Max Lemieux
终于完成了。谢谢兄弟。 - gogagubi

17

虽然这篇文章已经两个月了,但我今天遇到了这个问题,并搜索了几种解决方法。我想到的最有效的方法是使用useMemo来设置默认值,像这样:

const { control, errors, handleSubmit } = useForm({
    reValidateMode: 'onChange',
    defaultValues: useMemo(() => yourDefaultValues, [yourDefaultValues]),
});

这使您能够在表单中正确设置值,而不必担心如果您有字段数组(这是我的情况),则需要多次实现。

如果您使用官方文档中的高级智能表单组件示例,也可以使用此功能。如果您有任何问题,请告诉我!


1
我正在使用6.8.3版本,并且每次“yourDefaultValues”更改时,默认值都会重新评估,但表单上没有任何变化(我在useMemo中添加了调试语句)。我已经验证了组件的名称与组件的“name”相同,并且组件正在使用“inputRef”和“register”。你有什么想法吗? - Patrick Desjardins
1
重现链接:https://codesandbox.io/s/usereacthookformdefaultvalue-h6d71?file=/src/MyForm.tsx - Patrick Desjardins

9

这适用于嵌套对象(我正在使用版本6.15.1)。

useEffect(() => {
    for (const [key, value] of Object.entries(data)) {
        setValue(key, value, {
            shouldValidate: true,
            shouldDirty: true
        })
    }
}, [data])

最干净的实现方式,我认为。 - Ryan Walker
1
虽然,eslint 不喜欢 for of 循环。一个可能的解决方案是 Object.keys(data).forEach((val, i) => {}) - Ryan Walker
1
这个不起作用,当你重置表单时,你得到的是空字段而不是默认值。reset() 而不是 setValues 大多数情况下可以工作,但它不稳定,并且如果你有几个异步值,它会加载未定义的值。我仍在寻找正确的解决方案。 - marko kraljevic

4

使用reset是一个简单的解决方案。

const { reset } = useForm();


onClick={()=> reset({ firstname: 'Joe' }, { lastname: 'Doe' }) }

4
从 react-hook-form 7.41 开始,您可以像这样使用 async 函数与 defaultValues:
const {
  formState: { isLoading },
} = useForm({
  defaultValues: fetch('API'),
  // resetOptions: {
  //   keepDirtyValues: true
  // }
});

现在defaultValue字段的类型看起来像这样:
type AsyncDefaultValues<TFieldValues> = (payload?: unknown) => Promise<TFieldValues>;

isLoading 用于异步默认值加载状态。


最新的答案 - tykhan
如果你在"defaultValues"键内获取数据,那么要么是假设react-hook-form将成为你的状态管理工具,要么是在重复网络请求。如果你使用类似RTK Query或Zustand这样的工具,你应该在那里进行调用,并将数据传递给表单,而不是让react-hook-form成为数据真实来源。 - ajHurliman
我正在使用这种方法,如何将默认的图像/头像传递给像WebPhoto这样的控件? - undefined

1

文档提到了一种设置默认值的方法,如果状态在这里发生变化https://react-hook-form.com/api/useform/#values

function App({ values }) {
  useForm({
    values  // will get updated when values props updates       
  })
}

我使用上述方法重置表单时遇到了问题,因为它导致了无限循环。
react hook form Maximum update depth exceeded

所以,我尝试使用useEffect重置这些值,但仍然没有起作用。

现在,我意识到如果我要重置整个表单,那么我必须像下面这样做,它就可以工作了。

  useEffect(()=>{
    reset({...dataFromApi});
  }, [quote])


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接