Skip to content
Learni
View all tutorials
Frontend

How to Create a Form with React Hook Form in 2026

Lire en français

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

terminal
npm install react-hook-form
npm install -D @types/react

We 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

ContactForm.tsx
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

ContactForm.tsx
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

ContactForm.tsx
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

ContactForm.tsx
<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.