React Hook Form
Master React Hook Form for efficient form handling with minimal re-renders and built-in validation
80 min•By Priygop Team•Last updated: Feb 2026
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.
Basic Form Setup
Example
// 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
Example
// 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>
);
}