DEV Community

Cover image for React Hook Form with Zod: Complete Guide for 2026
Md. Maruf Rahman
Md. Maruf Rahman

Posted on • Edited on • Originally published at practicaldev.online

React Hook Form with Zod: Complete Guide for 2026

Form validation used to be my least favorite part of frontend development. Between managing state with useState, handling errors, and ensuring type safety, it felt like I was writing more boilerplate than actual logic. Then I discovered React Hook Form combined with Zod, and everything changed.

What makes React Hook Form so powerful isn't just that it reduces codeβ€”it's that it makes forms actually enjoyable to build. React Hook Form uses the useForm hook to handle all the performance optimizations (minimal re-renders, uncontrolled components), while Zod gives you runtime validation that matches your TypeScript types perfectly.

Why React Hook Form?

  • Minimal re-renders: Uses uncontrolled components with refs
  • Small bundle size: ~9KB (gzipped)
  • Excellent TypeScript support: Full type inference with Zod
  • Built-in validation: Works seamlessly with validation libraries
  • Better performance: Faster than alternatives like Formik

Installation

npm install react-hook-form @hookform/resolvers zod
Enter fullscreen mode Exit fullscreen mode

Building Your First Schema

Let's start with a real-world example: a product form:

import * as z from "zod";

const productSchema = z.object({
  name: z
    .string()
    .trim()
    .min(1, { message: "Required" })
    .min(2, { message: "Minimum 2 characters required" }),
  categoryId: z.string().trim().min(1, { message: "Required" }),
  price: z.string().trim().min(1, { message: "Required" }),
  stock: z.string().trim().min(1, { message: "Required" }),
  product_image: z.preprocess((val) => {
    if (!val) return null;
    if (val instanceof FileList) {
      const file = val.item(0);
      return file ?? null;
    }
    return val;
  }, z.instanceof(File).nullable().refine(
    (file) => file !== null, 
    { message: "Product image is required" }
  )),
});

type ProductFormData = z.infer<typeof productSchema>;
Enter fullscreen mode Exit fullscreen mode

Complete Form Example

Here's a production-ready React Hook Form component:

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

function AddProduct() {
  const [submitError, setSubmitError] = useState<string | null>(null);

  const {
    register,
    handleSubmit,
    watch,
    reset,
    formState: { errors, isSubmitting, isValid },
  } = useForm<ProductFormData>({
    defaultValues: {
      name: "",
      categoryId: "",
      price: "",
      stock: "",
      product_image: null,
    },
    resolver: zodResolver(productSchema),
    mode: "all",
  });

  const productImage = watch("product_image");

  const onSubmit = async (data: ProductFormData) => {
    try {
      setSubmitError(null);

      const formData = new FormData();
      formData.append("name", data.name.trim());

      if (data.product_image) {
        formData.append("product_image", data.product_image);
      }

      const response = await fetch("/api/products", {
        method: "POST",
        body: formData,
      });

      if (!response.ok) {
        throw new Error("Failed to create product");
      }

      reset();
    } catch (error) {
      setSubmitError(error instanceof Error ? error.message : "An error occurred");
    }
  };

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

      <input
        type="file"
        accept="image/*"
        {...register("product_image")}
      />
      {errors.product_image && <p>{errors.product_image.message}</p>}

      <button type="submit" disabled={isSubmitting || !isValid}>
        {isSubmitting ? "Creating..." : "Create Product"}
      </button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use Zod schemas for validation
  2. Leverage type inference from Zod
  3. Handle file uploads properly
  4. Show validation errors appropriately
  5. Use watch for dependent fields
  6. Reset forms after successful submission

πŸ“– Read the Complete Guide

This is just a brief overview! The complete guide on my blog includes:

  • βœ… Advanced Validation - Custom validators, async validation
  • βœ… File Uploads - Single and multiple file handling
  • βœ… Form Arrays - Dynamic form fields
  • βœ… Dependent Fields - Conditional validation
  • βœ… Error Handling - Complete error management patterns
  • βœ… Performance Optimization - Minimizing re-renders
  • βœ… Real-world examples from production applications

πŸ‘‰ Read the full article with all code examples here


What's your experience with React Hook Form? Share your tips in the comments! πŸš€

For more React guides, check out my blog covering TypeScript, React Router, Redux Toolkit, and more.

Top comments (0)