Professional React WorkflowExtra· 40 min read

Real Forms: React Hook Form & Zod

Build fast, validated forms with the library combo most React jobs now use instead of hand-wired controlled inputs.

What you will learn

  • Explain why teams use a form library
  • Register fields and submit with React Hook Form
  • Validate input with a Zod schema

Why a form library?

You built controlled inputs by hand: each field has its own state and onChange, and the whole form re-renders on every keystroke. That is great for learning, but real forms have many fields, validation rules (required, valid email, password length), and error messages — and writing all that by hand gets long and re-renders a lot. React Hook Form (imported as react-hook-form) handles the wiring for you with far fewer re-renders, and Zod describes your validation rules as a reusable schema.

A schema is just a description of what valid data looks like — “name is a non-empty string, email must look like an email, age is a number at least 18”. Zod checks real input against that description and produces friendly error messages.

Set up

Install the form library, the validator, and the bridge between them
npm install react-hook-form zod @hookform/resolvers

Three packages: react-hook-form (the form engine), zod (the validator), and @hookform/resolvers (a small bridge that lets React Hook Form use a Zod schema). This is real project code, shown with Output.

A validated sign-up form

React Hook Form + Zod: register, validate, submit
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// 1) Describe valid data
const schema = z.object({
  name:  z.string().min(2, 'Name is too short'),
  email: z.string().email('Enter a valid email'),
  age:   z.coerce.number().min(18, 'Must be 18 or older')
});

function SignUp() {
  const { register, handleSubmit, formState: { errors } } =
    useForm({ resolver: zodResolver(schema) });

  function onValid(data) {
    console.log('Valid form:', data);
  }

  return (
    <form onSubmit={handleSubmit(onValid)}>
      <input {...register('name')} placeholder="Name" />
      <p>{errors.name?.message}</p>

      <input {...register('email')} placeholder="Email" />
      <p>{errors.email?.message}</p>

      <input {...register('age')} placeholder="Age" />
      <p>{errors.age?.message}</p>

      <button type="submit">Sign up</button>
    </form>
  );
}

How the pieces fit together:

  1. The Zod schema z.object({ ... }) describes valid data: name must be a string of at least 2 characters, email must look like an email, and age (after z.coerce.number() turns the text input into a number) must be at least 18. Each rule carries the message to show if it fails.
  2. useForm({ resolver: zodResolver(schema) }) creates the form and tells it to validate against that schema. It hands back register (to connect inputs), handleSubmit (to run on submit), and errors (the current validation errors).
  3. Each input is wired with {...register('name')} — that one spread connects the field to the form (value, onChange, and more) with no manual state.
  4. Under each input, {errors.name?.message} shows that field’s error message if validation failed (the ?. safely shows nothing when there is no error).
  5. handleSubmit(onValid) first runs Zod validation; onValid only runs if every rule passes, receiving the clean data object.

Note: Output: A form with Name, Email and Age fields. Submitting with an empty name shows Name is too short; a bad email shows Enter a valid email; age 16 shows Must be 18 or older. Once every field is valid, the console logs the clean object, e.g. { name: 'Asha', email: 'a@x.com', age: 20 }.

Tip: A big bonus: the same Zod schema can validate on the server too (in your Node/Express API), so your front-end and back-end agree on what valid data is — write the rules once.

Watch out: Always validate again on the server as well. Client-side validation is for a nice user experience, but a user can bypass it — never trust the browser alone for real data.

Q. What does a Zod schema describe?

Answer: A Zod schema declares what valid data looks like (types and rules) and produces error messages; React Hook Form uses it to validate the form.

✍️ Practice

  1. Build a login form (email, password) with React Hook Form and a Zod schema requiring a valid email and a password of at least 6 characters.
  2. Show each field’s error message under its input.

🏠 Homework

  1. Rebuild an earlier controlled form using React Hook Form + Zod and write two sentences on what got simpler.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →