How to change React-Hook-Form defaultValue with useEffect()?

69,868

Solution 1

@tam answer is halfway through what is needed to make it work with version 6.8.3.

You need to provide the default value but also to useEffect to reset. That particular distinction is required if you have a form that you reload with another entity. I have a complete example in CodeSanbox here.

In a nutshell: You need to define your defaultValues in the userForm.

 const { register, reset, handleSubmit } = useForm({
    defaultValues: useMemo(() => {
      return props.user;
    }, [props])
  });

Then you need to listen to potential change.

  useEffect(() => {
    reset(props.user);
  }, [props.user]);

The example in the Code Sandbox allows swapping between two users and have the form change its values.

Solution 2

setValue didn't work for me. Alternatively, you can use the reset method:

Reset either the entire form state or part of the form state.

Here is working code:

 /* 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]); 

Solution 3

@tommcandrew's setValue parameter formatting didn't work for me.

This format did:

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

Solution 4

although this post is 2 months old, I stumbled upon this issue today and searched for a couple of ways to do it. The most effective way I've come up with is using useMemo to set your defaultValues, like this :

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

This allows you to properly set values in your form, without the struggle of multiple implementations if you happen to have field arrays (which was my case).

This also works while using the advanced smart form component exemple from the official documentation. Let me know if you have any questions !

Solution 5

This works for nested objects (I'm using version 6.15.1)

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

theedchen

Hello World !!

Updated on October 12, 2021

Comments

  • theedchen
    theedchen over 2 years

    I am creating a page for user to update personal data with React-Hook-Form. Once paged is loaded, I use useEffect to fetch the user's current personal data and set them into default value of the form.

    I put the fetched value into defaultValue of <Controller />. However, it is just not showing in the text box. Here is my code:

    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;

    The called API is working well and the value is actually set to userData state.

    {
      name: "John",
      phone: "02-98541566"
      ...
    }
    

    I also tried to setUserData with mock data in useEffect(), and it doesn't work either. Is there any problem in my above code?

  • Max Lemieux
    Max Lemieux over 3 years
    This is working for me with React Native and a controller wrapper.
  • Patrick Desjardins
    Patrick Desjardins about 3 years
    I am running on 6.8.3 and the default value gets reevaluated each time the yourDefaultValues change but nothing happens on the form (I added debug statement in the useMemo). I have verified that the name are the same from the name of the component and the components are using inputRefand register. Do you have any idea?
  • Patrick Desjardins
    Patrick Desjardins about 3 years
  • Dev
    Dev about 3 years
    This is definitely one of the cleaner answers here.
  • dev
    dev about 3 years
    @ Patrick, this works. Just a doubt when i tried without wrapping in the useMemo also it worked, the reason why we are using useMemo is that it won't recalculate on the next render until unless any of the dependency changed. Also any other diff using a useMemo and without using a useMemo directly passing the object since useEffect is already there? Can you correct me if this wrong - New to react :)
  • gogagubi
    gogagubi about 3 years
    At last. I did it. Thanks bro
  • Ryan Walker
    Ryan Walker almost 3 years
    cleanest implementation imo
  • Ryan Walker
    Ryan Walker almost 3 years
    Although, eslint won't like the for of loop. A possible solution is Object.keys(data).forEach((val, i) => {})
  • Jaeeun Lee
    Jaeeun Lee over 2 years
    Just curious why are you using useMemo here?
  • Bon Andre Opina
    Bon Andre Opina over 2 years
    This is correct.
  • RollingInTheDeep
    RollingInTheDeep over 2 years
    You dont need useEffect, just do this when you have recieved the apiData directly in the .then section.
  • Harish Kulkarni
    Harish Kulkarni over 2 years
    That works too, It depends on Implementation as usually API calls and handling data are made in different files. @RollingInTheDeep
  • BaronVonKaneHoffen
    BaronVonKaneHoffen about 2 years
    Thanks so much for this. Works great!
  • marko kraljevic
    marko kraljevic almost 2 years
    this doesnt work, when you reset form you get empty fields and not with defaultValues. reset() instead of setValues mostly works but its flaky and loads undefined async values if you have few of them, i am still looking for proper solution