React Hook Form
Master React Hook Form for efficient form handling with minimal re-renders and built-in validation. This is a foundational concept in component-based UI development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React experience. Take your time with each section and practice the examples
What is React Hook Form?
React Hook Form is a performant, flexible and extensible form library with easy-to-use validation. It reduces the amount of code you need to write while removing unnecessary re-renders.. This is an essential concept that every React developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Basic Form Setup
// Install React Hook Form
npm install react-hook-form
// Basic form
import { useForm } from 'react-hook-form';
function BasicForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
} = useForm();
const onSubmit = async (data) => {
console.log('Form data:', data);
// Handle form submission
try {
await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
reset(); // Reset form after successful submission
} catch (error) {
console.error('Submission failed:', error);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Name</label>
<input
id="name"
{...register('name', {
required: 'Name is required',
minLength: { value: 2, message: 'Name must be at least 2 characters' }
})}
/>
{errors.name && <span className="error">{errors.name.message}</span>}
</div>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address'
}
})}
/>
{errors.email && <span className="error">{errors.email.message}</span>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
{...register('password', {
required: 'Password is required',
minLength: { value: 8, message: 'Password must be at least 8 characters' }
})}
/>
{errors.password && <span className="error">{errors.password.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}Advanced Form Features
// Advanced React Hook Form features
import { useForm, Controller } from 'react-hook-form';
// Form with custom validation
function AdvancedForm() {
const {
control,
register,
handleSubmit,
formState: { errors, isValid, isDirty },
watch,
setValue,
getValues,
trigger,
} = useForm({
mode: 'onChange', // Validate on change
criteriaMode: 'all', // Show all validation errors
});
const watchedPassword = watch('password');
const onSubmit = async (data) => {
console.log('Form data:', data);
};
// Custom validation function
const validatePassword = (value) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
if (!/[A-Z]/.test(value)) return 'Password must contain at least one uppercase letter';
if (!/[a-z]/.test(value)) return 'Password must contain at least one lowercase letter';
if (!/[0-9]/.test(value)) return 'Password must contain at least one number';
return true;
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
{...register('username', {
required: 'Username is required',
minLength: { value: 3, message: 'Username must be at least 3 characters' },
pattern: {
value: /^[a-zA-Z0-9_]+$/,
message: 'Username can only contain letters, numbers, and underscores'
}
})}
/>
{errors.username && <span className="error">{errors.username.message}</span>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
{...register('password', {
validate: validatePassword
})}
/>
{errors.password && <span className="error">{errors.password.message}</span>}
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password</label>
<input
id="confirmPassword"
type="password"
{...register('confirmPassword', {
required: 'Please confirm your password',
validate: (value) => value === watchedPassword || 'Passwords do not match'
})}
/>
{errors.confirmPassword && <span className="error">{errors.confirmPassword.message}</span>}
</div>
{/* Using Controller for complex inputs */}
<div>
<label htmlFor="country">Country</label>
<Controller
name="country"
control={control}
rules={{ required: 'Country is required' }}
render={({ field }) => (
<select {...field}>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
<option value="au">Australia</option>
</select>
)}
/>
{errors.country && <span className="error">{errors.country.message}</span>}
</div>
<div>
<label htmlFor="bio">Bio</label>
<Controller
name="bio"
control={control}
rules={{ maxLength: { value: 500, message: 'Bio must be less than 500 characters' } }}
render={({ field }) => (
<textarea
{...field}
placeholder="Tell us about yourself..."
rows={4}
/>
)}
/>
{errors.bio && <span className="error">{errors.bio.message}</span>}
</div>
<button type="submit" disabled={!isValid || !isDirty}>
Submit
</button>
<button type="button" onClick={() => trigger()}>
Validate All
</button>
</form>
);
}