Setting Default Values in React Hook Form with useEffect
When building forms with React Hook Form, you often need to populate form fields with data fetched asynchronously from an API. This guide covers the best practices for setting default values using useEffect() and the latest React Hook Form features.
The Core Problem
React Hook Form's defaultValue prop only works during the initial render. If you try to set default values after an asynchronous data fetch using useEffect, the form won't automatically update with the fetched data:
// ❌ Doesn't work - defaultValue won't update after initial render
<Controller
as={<input type='text' />}
control={control}
defaultValue={userData ? userData.name : ''} // This won't update
name='name'
/>Best Solutions
1. Using the reset Method (Recommended)
The most reliable approach is to use the reset method provided by useForm:
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 userData = await response.json();
setUserData(userData);
// Reset form with fetched data
reset({
name: userData.name,
phone: userData.phone
});
};
fetchUserData();
}, [reset]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={<input type='text' />}
control={control}
name='name'
/>
{/* Other form fields */}
</form>
);
};TIP
The reset method completely replaces the form values, making it ideal for initial data loading or complete form resets.
2. Using the values Property (v7.0+)
React Hook Form v7 introduced a values property that reactively updates the form:
function UpdateUserData() {
const [userData, setUserData] = useState(null);
useForm({
defaultValues: {
name: "",
phone: ""
},
values: userData // Reactively updates when userData changes
});
// Fetch logic remains the same
}3. Async Default Values (v7.47+)
For newer versions, you can directly use async functions in defaultValues:
const { formState: { isLoading } } = useForm({
defaultValues: async () => {
const account = localStorage.getItem('account');
const response = await fetch(`${URL}/user/${account}`);
return response.json();
}
});WARNING
Async defaultValues only work on component mount. For updates after mount, use reset() or the values property.
4. Parent-Component Pattern
For cleaner architecture, fetch data in a parent component and pass it as props:
// Parent component
const UserFormContainer = () => {
const [userData, setUserData] = useState(null);
useEffect(() => {
// Fetch data here
}, []);
return userData ? <UserForm defaultValues={userData} /> : <LoadingSpinner />;
};
// Form component
const UserForm = ({ defaultValues }) => {
const { register, handleSubmit } = useForm({
defaultValues // Set once when component mounts
});
// Form JSX
};Common Pitfalls and Solutions
Infinite Loops with values
Using the values property can sometimes cause infinite loops:
// ❌ Might cause infinite loop
useForm({
values: userData // Could trigger endless re-renders
});
// ✅ Better: Use reset in useEffect
useEffect(() => {
if (userData) {
reset(userData);
}
}, [userData, reset]);Setting Individual Fields with setValue
For updating individual fields without resetting the entire form:
const { setValue } = useForm();
useEffect(() => {
if (userData) {
setValue("name", userData.name, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true
});
}
}, [userData, setValue]);Working with Nested Objects
For complex data structures, iterate through object properties:
useEffect(() => {
if (data) {
for (const [key, value] of Object.entries(data)) {
setValue(key, value, {
shouldValidate: true,
shouldDirty: true
});
}
}
}, [data]);Comparison of Approaches
| Method | Best For | Version | Notes |
|---|---|---|---|
reset() | Complete form updates | All versions | Most reliable approach |
values property | Reactive updates | v7.0+ | Can cause infinite loops |
Async defaultValues | Initial data loading | v7.47+ | Only works on mount |
| Parent pattern | Clean architecture | All versions | Separates concerns |
Conclusion
The optimal approach for setting default values with useEffect depends on your React Hook Form version and use case:
- For most cases: Use
reset()insideuseEffect - For modern versions (v7.47+): Consider async
defaultValuesfor initial loading - For architectural purity: Use the parent component pattern
- For individual field updates: Use
setValuewith options
Always remember that defaultValue props on form elements only work during initial rendering. For dynamic updates after mounting, you need to use React Hook Form's imperative methods like reset or setValue.
INFO
Check the React Hook Form documentation for the latest updates and best practices, as the library evolves rapidly.