admin管理员组

文章数量:1128437

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;

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?

Share Improve this question edited Oct 30, 2020 at 6:26 theedchen asked Jun 7, 2020 at 8:16 theedchentheedchen 2,0064 gold badges11 silver badges20 bronze badges 1
  • sorry but i didn't get what are you trying to achieve can you explain more. – adel Commented Jun 7, 2020 at 8:57
Add a comment  | 

18 Answers 18

Reset to default 201

@tam's answer is halfway to 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 useForm:

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.

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.

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

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.

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

This format did:

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

I know this post is a few years old but I stumbled upon it and found that there is now a better way than the accepted answer.

The documentation now has a prop called values that will reactively update (see https://react-hook-form.com/docs/useform#values).

Here is the example code:

function App() {
  const values = useFetch("/api")

  useForm({
    defaultValues: {
      firstName: "",
      lastName: "",
    },
    values, // will get updated once values returns, this can be a state variable like OP's userData state variable.
  })
}

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 !

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])

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.

Using reset is a simple solution.

const { reset } = useForm();


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

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])

If you are using version 7, you may no longer need useEffect to do "loading defaultValue from async call (e.g. fetch)" on the form component mounts

example from v7.47 doc:

// set default value async
useForm({
  defaultValues: async () => fetch('/api-endpoint');
})

note:

  1. that async function defined in defaultValues should return the object required for defaultValues
  2. the call (defaultValues()) will be made on component mounts (same as useEffect with empty dependency array)
  3. if you need other dependency, use reset() to load your defaultValues (because they are cached)

There is a much better way to do this that I may have missed in the comments or in one of the answers.

Just send your defaultValues in as a prop to the form itself.

This higher order pattern works quite a bit better.

<UpdateAppointmentForm defaultValues={query.data} />

When I fetch from my system, I have the model I'm working with populated. Passing it directly down to the function ensures it re-renders with the data in the way I need.

In the <UpdateAppointmentForm> component, I can just use the useForm hook with the values instantiated:

interface Props {
  defaultValues: SchemaType;
}

export const UpdateAppointmentForm = (props: Props) => {

  const form = useForm<SchemaType>({
    resolver: zodResolver(Schema),
    reValidateMode: "onBlur",
    shouldFocusError: true,
    defaultValues,
    //...other stuff
  });

  // ... return your form
}

The approaches in here to manipulate the state on the fly just add unnecessary complication and other state juggling you really don't need to worry about. Let React's rendering cycle and component tree do the work for you.

Happy coding!

Easiest method for those interested,

// fetching data from clerk

const { user } = useUser();

// Creating default values based on information I am receiving from the parent component.

const defaultValues = initialData
    ? initialData
    : {
      name: "",
      stock: 0,
      description: '',
      negotiable: false,
      discount: 0,
      categoryId: "",
      creatorId: '',
      isFeatured: false,
      isArchived: false,
      sizes: [],
      colors: [],
      price: 0,
      images: [],
    };

//I removed defaultValues from the form as this only renders the values into the hook form once. The 'values' function on the other hand re-renders on input change and asynchronously adds your data to the form. Also note that since the data being fetched is undefined at first, I had to give it a fallback value of an empty string.

const form = useForm<ProductFormValues>({
    resolver: zodResolver(formSchema),
    values: { ...defaultValues, creatorId: user?.id || '' },
  });

Many comments telling you to just "setValue" or "reset". But that's not appropriate.

My recommendation:

Wrap you form in a Component, fetch the data on parent Component and render your form only when your data is ready. Use "defaultValues"

example:

const UserForm = ({data}) => {
  const {...} = useForm({
    defaultValues:data
  })
}

const ParentForm = () => {
  const userData = fetch('...');

  return userData ? <UserForm data={userData} /> : <Loading />;
};
 // single value change
    useEffect(() => {
        setValue("username", 'changed username', {
            shouldValidate: true,
            shouldDirty: true,
            shouldTouch: true
        })
    }, []);

we can set the default values of hook forms using reset provided by react-hook-forms

const { handleSubmit, control,reset} = useForm();
    const fetchUserData = useCallback(async account => {
        const userData = await fetch(`${URL}/user/${account}`)
                            .then(res=> res);
        console.log(userData);
        if(userData.status===200||userData.status===201){
            reset(userData.data);
        }
    }, []);
useEffect(() => {
        const account = localStorage.getItem('account');
        fetchUserData(account);
        
    }, [fetchUserData])

this sets the values of the fields in form with the data fetched. this works only when the field names in both form and fetched matches. you can also specify the type of data for forms using interface if you are using typescript.

interface

interface props{
name:string,
id:number
}

useForm

const { handleSubmit, control,reset} = useForm<props>();

This solution worked for me

sessionData is the result of fetching data

useEffect(() => {
  sessionData &&
    reset({
      name: sessionData?.user?.name,
      username: sessionData?.user?.username,
      email: sessionData?.user?.emails.email,
      password: "",
      confirmPassword: "",
    });
}, [sessionData]);

本文标签: javascriptHow to change ReactHookForm defaultValue with useEffect()Stack Overflow