Skip to content

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:

jsx
// ❌ 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

The most reliable approach is to use the reset method provided by useForm:

jsx
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:

jsx
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:

jsx
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:

jsx
// 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:

jsx
// ❌ 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:

jsx
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:

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

Comparison of Approaches

MethodBest ForVersionNotes
reset()Complete form updatesAll versionsMost reliable approach
values propertyReactive updatesv7.0+Can cause infinite loops
Async defaultValuesInitial data loadingv7.47+Only works on mount
Parent patternClean architectureAll versionsSeparates concerns

Conclusion

The optimal approach for setting default values with useEffect depends on your React Hook Form version and use case:

  1. For most cases: Use reset() inside useEffect
  2. For modern versions (v7.47+): Consider async defaultValues for initial loading
  3. For architectural purity: Use the parent component pattern
  4. For individual field updates: Use setValue with 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.