Learn and Teach Coding
ReactIntermediateMay 18, 2024 · 10 min read

React Form State Management with React Hook Form

Manage complex form state with minimal re-renders using React Hook Form, plus schema validation with Zod.

Controlled inputs re-render the whole form on every keystroke. For a small form that's fine; for a large one it gets slow. React Hook Form keeps values in refs and only re-renders when it has to.

Registering fields

import { useForm } from "react-hook-form";

type Values = { email: string; password: string };

export function SignInForm() {
  const { register, handleSubmit } = useForm<Values>();

  return (
    <form onSubmit={handleSubmit((values) => console.log(values))}>
      <input {...register("email")} />
      <input type="password" {...register("password")} />
      <button type="submit">Sign in</button>
    </form>
  );
}

register wires the input up with an uncontrolled ref — no value/onChange needed.

Validation with Zod

Define the rules once and get types and runtime validation from the same schema:

import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  password: z.string().min(8, "At least 8 characters"),
});

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

Because z.infer<typeof schema> derives the form type from the schema, your validation rules and TypeScript types can never drift apart.

Showing errors

<input {...register("email")} />
{errors.email && <p role="alert">{errors.email.message}</p>}

See the full version, including the submit state, in the React Login Form Validation example.

Related tutorials