Introduction
React Hook Form is the most popular library for managing forms in React. It reduces re-renders and simplifies validation. This tutorial teaches you how to create a complete contact form with TypeScript, native validation, and error handling. You'll go from zero to a production-ready form by following concrete steps.
Prerequisites
- Node.js 18+
- React 18+
- Basic knowledge of TypeScript
- An existing React project (Vite or Create React App)
Installing Dependencies
npm install react-hook-form
npm install -D @types/reactWe install React Hook Form. The @types/react package is useful for TypeScript but is often already present in recent React projects.
Basic Form Setup
We will now create the form component. Use the useForm hook to initialize the form and retrieve the register, handleSubmit, and formState methods.
Contact Form Component
import React from 'react';
import { useForm } from 'react-hook-form';
type FormData = {
name: string;
email: string;
message: string;
};
export default function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>();
const onSubmit = (data: FormData) => {
console.log('Données soumises:', data);
alert('Formulaire envoyé avec succès !');
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name', { required: 'Le nom est requis' })} placeholder="Votre nom" />
{errors.name && <p>{errors.name.message}</p>}
<input type="email" {...register('email', { required: 'L\'email est requis' })} placeholder="Votre email" />
{errors.email && <p>{errors.email.message}</p>}
<textarea {...register('message', { required: 'Le message est requis', minLength: { value: 10, message: 'Minimum 10 caractères' } })} placeholder="Votre message" />
{errors.message && <p>{errors.message.message}</p>}
<button type="submit">Envoyer</button>
</form>
);
}This code creates a complete form with three fields, required validation, and error display. register connects each input to React Hook Form's internal state.
Adding Email Validation
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
defaultValues: {
name: '',
email: '',
message: ''
}
});Add defaultValues to prefill the form. This avoids controlled/uncontrolled component issues.
Handling Async Submission
const onSubmit = async (data: FormData) => {
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Erreur serveur');
alert('Message envoyé !');
} catch (error) {
alert('Une erreur est survenue');
}
};Make the onSubmit function asynchronous to call an API. Handle network errors with try/catch.
Adding Styles and Accessibility
<input
{...register('email')}
className="border p-2 w-full rounded"
aria-invalid={errors.email ? 'true' : 'false'}
/>Add Tailwind or CSS classes and the aria-invalid attribute to improve accessibility and visual error feedback.
Best Practices
- Always type FormData with TypeScript
- Use defaultValues to avoid unnecessary renders
- Separate validation logic into a dedicated file
- Disable the button during submission with isSubmitting
- Test the form with React Testing Library
Common Mistakes to Avoid
- Forgetting to pass handleSubmit to onSubmit
- Not typing form data
- Manually using value + onChange (anti-pattern)
- Ignoring error messages in the UI
Going Further
Check out our advanced React courses to master Zod, useFieldArray, and form testing.