
One of the most common challenges when building forms is managing dependent fields—fields whose values or visibility depend on the value of another field. For example, selecting a country might determine which cities are available in a dropdown. In this article, we’ll explore how to handle dependent fields in react-hook-form effectively, ensuring a smooth and dynamic user experience. Let’s dive in!
Imagine you’re building a form where:
- The user selects a country from a dropdown.
- Based on the selected country, a second dropdown displays a list of cities.
- The city field should only be visible if a country is selected.
Without proper handling, this can lead to several issues:
- Outdated Data: The city dropdown might not update when the country changes, showing incorrect or stale options.
- Validation Errors: If the city field is required but not yet visible, the form might throw validation errors prematurely.
- Performance Bottlenecks: Frequent re-renders caused by watching multiple fields can slow down your form, especially in large or complex applications.
- User Experience: A poorly implemented dependent field can confuse users, leading to frustration and errors.
Why this problem occurs?
In React, when a component’s state or props change, the entire component re-renders. If you’re not careful, watching a field’s value (e.g., the country) can trigger unnecessary re-renders of the entire form, even if only a small part of it needs to update.
react-hook-form is designed to minimize re-renders by isolating form state. However, when dealing with dependent fields, you need to explicitly manage how and when fields update based on changes to other fields. Without proper management, the form can become inconsistent or inefficient.
Using watch and Conditional Rendering
To handle dependent fields in react-hook-form, you can use the watch function to monitor the value of the country field and conditionally render the city field based on its value. Here’s how to implement it step by step.
Step 1: set up the form
Start by setting up the form with useForm and defining the fields. Use the watch function to monitor the value of the country field.
import { useForm, Controller } from 'react-hook-form';
const countries = [
{ id: 'us', name: 'United States' },
{ id: 'ca', name: 'Canada' },
];
const cities = {
us: ['New York', 'Los Angeles', 'Chicago'],
ca: ['Toronto', 'Vancouver', 'Montreal'],
};
const MyForm = () => {
const { control, watch, handleSubmit } = useForm();
const selectedCountry = watch('country'); // Watch the country field
const onSubmit = (data) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Country Dropdown */}
<Controller
name="country"
control={control}
defaultValue=""
render={({ field }) => (
<select {...field}>
<option value="">Select a country</option>
{countries.map((country) => (
<option key={country.id} value={country.id}>
{country.name}
</option>
))}
</select>
)}
/>
{/* City Dropdown (Conditional)} */}
{selectedCountry && (
<Controller
name="city"
control={control}
defaultValue=""
render={({ field }) => (
<select {...field}>
<option value="">Select a city</option>
{cities[selectedCountry].map((city) => (
<option key={city} value={city}>
{city}
</option>
))}
</select>
)}
/>
)}
<button type="submit">Submit</button>
</form>
);
};
Step 2: add validation
If the city field is required but only visible when a country is selected, you need to conditionally apply validation rules. This ensures that the city field is only validated when it’s visible. Simply add the rules prop to the Controller for the city field:
<Controller
name="city"
control={control}
defaultValue=""
rules={{ required: 'City is required' }} // <- Add required rules
...
/>
Step 3: optimize performance
If the form has many dependent fields or complex logic, you can optimize performance by isolating re-renders using useWatch instead of watch. useWatch allows you to subscribe to specific fields without triggering re-renders for the entire form.
const selectedCountry = useWatch({ control, name: 'country' }); // Isolate re-renders
Final code:
import { useForm, Controller, useWatch } from 'react-hook-form';
const MyForm = () => {
const { control, handleSubmit, formState: { errors } } = useForm();
const selectedCountry = useWatch({ control, name: 'country' }); // Isolate re-renders
const onSubmit = (data) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Country Dropdown */}
<Controller
name="country"
control={control}
defaultValue=""
rules={{ required: 'Country is required' }}
render={({ field }) => (
<div>
<select {...field}>
<option value="">Select a country</option>
{countries.map((country) => (
<option key={country.id} value={country.id}>
{country.name}
</option>
))}
</select>
{errors.country && <span>{errors.country.message}</span>}
</div>
)}
/>
{/* City Dropdown (Conditional)} */}
{selectedCountry && (
<Controller
name="city"
control={control}
defaultValue=""
rules={{ required: 'City is required' }}
render={({ field }) => (
<div>
<select {...field}>
<option value="">Select a city</option>
{cities[selectedCountry].map((city) => (
<option key={city} value={city}>
{city}
</option>
))}
</select>
{errors.city && <span>{errors.city.message}</span>}
</div>
)}
/>
)}
<button type="submit">Submit</button>
</form>
);
};
Conclusion
Handling dependent fields in forms is a common but challenging task, especially when building dynamic and user-friendly interfaces. By leveraging react-hook-form’s powerful features like watch, useWatch, and conditional rendering, you can create forms that respond intelligently to user input. These tools allow you to manage field dependencies efficiently, ensuring that your forms are both functional and performant.
Moreover, by conditionally applying validation rules and optimizing re-renders, you can prevent common pitfalls like validation errors and performance bottlenecks. Whether you’re building a simple form or a complex multi-step workflow, mastering these techniques will help you deliver a seamless user experience. With react-hook-form, you have the flexibility and control to handle even the most intricate form scenarios with ease.