127

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?

theedchen
  • 1,564
  • 4
  • 10
  • 17

10 Answers10

146

@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.

Patrick Desjardins
  • 136,852
  • 88
  • 292
  • 341
  • 8
    This is definitely one of the cleaner answers here. – Devin B. Mar 02 '21 at 07:24
  • @ 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 :) – dev Mar 03 '21 at 17:13
  • 7
    Just curious why are you using useMemo here? – Jaeeun Lee Oct 22 '21 at 17:40
  • This is correct. – Bon Andre Opina Nov 24 '21 at 11:05
  • Please explain why useMemo is used – Saral Karki Nov 11 '22 at 06:34
  • You don't have to put second snippet in useEffect. Component will rerender on props change anyway. It would've work without wrapping in useEffect. – Ali Mert Çakar Apr 10 '23 at 11:41
  • Hi @Patricks, can u please give me this question answer please https://stackoverflow.com/questions/76859696/react-hook-form-dynamic-add-form-is-not-working-properly – Rohit Azad Malik Aug 08 '23 at 15:14
85

You can use setValue (https://react-hook-form.com/api/useform/setvalue).

Import it from useForm:

const { handleSubmit, control, setValue} = useForm({ mode: 'onBlur' });

Then call it with the user data after it's received:

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

You can remove the default values from the form.

EDIT: See alternative answers below if this does not work.

Akash Kumar Verma
  • 3,185
  • 2
  • 16
  • 32
53

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]); 
Akash Kumar Verma
  • 3,185
  • 2
  • 16
  • 32
Kasim ŞEN
  • 743
  • 7
  • 10
  • Thanks for this, `reset({ ...reduxState })` is what I had been looking for to retain consistency between formState and reduxState. – deadbyte Feb 11 '23 at 20:49
33

Found another easy way, I used reset API from useForm

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

After you call API and get back response data, you call reset with new apiData, make sure apiData key's are same as input keys (name attribute):

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

form's default values are cached and hence once you get the data from API, we reset the form state with new data.

Harish Kulkarni
  • 1,481
  • 11
  • 18
18

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

This format did:

useEffect(() => {
  const object = localStorage.getItem('object');
  setValue("name", object.name);
}, [])
gts
  • 421
  • 4
  • 7
15

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 !

Tam
  • 314
  • 2
  • 5
  • 1
    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 `inputRef`and `register`. Do you have any idea? – Patrick Desjardins Feb 18 '21 at 20:02
  • 1
    Repro link: https://codesandbox.io/s/usereacthookformdefaultvalue-h6d71?file=/src/MyForm.tsx – Patrick Desjardins Feb 18 '21 at 20:20
9

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])
Akash Kumar Verma
  • 3,185
  • 2
  • 16
  • 32
iMyke
  • 573
  • 1
  • 7
  • 10
  • cleanest implementation imo – Ryan Walker Aug 16 '21 at 21:10
  • 1
    Although, eslint won't like the `for of` loop. A possible solution is `Object.keys(data).forEach((val, i) => {})` – Ryan Walker Aug 16 '21 at 21:30
  • 1
    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 – marko kraljevic Jun 27 '22 at 09:02
4

Using reset is a simple solution.

const { reset } = useForm();


onClick={()=> reset({ firstname: 'Joe' }, { lastname: 'Doe' }) }
Gucal
  • 842
  • 1
  • 11
  • 19
4

As of react-hook-form 7.41, you can use defaultValues with async functions like this:

const {
  formState: { isLoading },
} = useForm({
  defaultValues: fetch('API'),
  // resetOptions: {
  //   keepDirtyValues: true
  // }
});

now the defaultValue field type look like this:

type AsyncDefaultValues<TFieldValues> = (payload?: unknown) => Promise<TFieldValues>;

isLoading for the async defaultValues loading state.

Mert
  • 113
  • 1
  • 2
  • 5
  • The most up to date answer – tykhan Apr 30 '23 at 19:44
  • If you're fetching data inside the defaultValues key, this is either assuming that react-hook-form is going to be your state management tool or you're duplicating network requests. If you were using something like RTK Query, or Zustand, you'd want to make your calls in there and pass the data on to the forms, not having react-hook-form be the source of truth for your data. – ajHurliman Jul 26 '23 at 18:17
0

Documentation mentions a way to set the default values if the state changes here https://react-hook-form.com/api/useform/#values

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

I have had issues reseting the form using above method, as it is leading to an infinite loop.

react hook form Maximum update depth exceeded

So, instead i tried to reset these values using useEffect, but still it did not worked.

Now, i realised that if i have to reset entire form, then i have did something like below and it worked.

  useEffect(()=>{
    reset({...dataFromApi});
  }, [quote])
kshashank
  • 171
  • 1
  • 4