React: From Components to Production · Forms and Validation

Complex Forms with React Hook Form and Zod

Validate with a schema shared between client and server.

For large forms with complex validation, React Hook Form keeps inputs mostly uncontrolled (few re-renders) and manages errors, dirty state and submission. Pair it with Zod schemas via @hookform/resolvers so types and validation rules come from one source, which can also validate on the server.

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  name: z.string().min(2, "At least 2 characters"),
  email: z.string().email("Invalid email"),
  age: z.coerce.number().int().min(18, "Must be 18+"),
});
type FormValues = z.infer<typeof schema>;

export function ProfileForm({ onSave }: { onSave: (v: FormValues) => Promise<void> }) {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormValues>({
    resolver: zodResolver(schema),
  });
  return (
    <form onSubmit={handleSubmit(onSave)} noValidate>
      <label>Name <input {...register("name")} aria-invalid={!!errors.name} /></label>
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <label>Email <input type="email" {...register("email")} aria-invalid={!!errors.email} /></label>
      {errors.email && <p role="alert">{errors.email.message}</p>}
      <label>Age <input type="number" {...register("age")} /></label>
      {errors.age && <p role="alert">{errors.age.message}</p>}
      <button disabled={isSubmitting}>Save</button>
    </form>
  );
}

Common pitfalls

  • Duplicating validation rules between client and server instead of sharing schemas.
  • Not mapping server-side errors back to fields (setError).
  • Forgetting z.coerce for numeric inputs (HTML inputs return strings).

Key points

  • React Hook Form minimizes re-renders
  • Zod gives types + validation
  • Always validate again on the server

Further reading: React Hook Form: Get started

Practice questions

Why is React Hook Form efficient for big forms?

Answer

It registers mostly uncontrolled inputs and reads values via refs, so typing doesn't re-render the whole form; only components subscribed to specific state update.

What's the benefit of z.infer<typeof schema>?

Answer

The TypeScript type for form values is derived from the validation schema, so types and validation rules can't drift apart.

Open in the app to mark lessons complete, take notes and track your progress.