Build Type-Safe React Forms with Zod v4 and React Hook Form v7


Combining Zod with React Hook Form (RHF) creates a powerful and developer-friendly approach to building modern forms in React.

Zod brings end-to-end type safety—ensuring your form data always matches your TypeScript definitions—while RHF handles the form state, validation, and performance with minimal re-renders.

Together, they eliminate repetitive validation logic, reduce boilerplate code, and make error handling both predictable and maintainable.

By the end of this guide, you’ll not only understand how these two libraries complement each other but also build and deploy a fully functional, type-safe form online—complete with client-side validation, error messages, and production-ready structure.

Setting Up the Project

To get started, install the core libraries required for building and validating your form:

npm install react-hook-form zod @hookform/resolvers
  • react-hook-form : Handles form state and validation efficiently with minimal re-renders. It provides hooks like useForm that make managing inputs, errors, and submissions simple and performant.
  • zod : A TypeScript-first schema validation library. You’ll use it to define the shape of your form data, set validation rules, and ensure full type safety across your application.
  • @hookform/resolvers: Acts as a bridge between React Hook Form and validation libraries like Zod. It allows you to plug Zod directly into RHF so validation runs automatically when users interact with the form.

Once installed, you’re ready to connect Zod and React Hook Form to create a fully type-safe form with clean, minimal code.

Creating and Importing Files

Let’s start by creating a new file for your form component, for example: src/components/LoginForm.tsx

Inside this file, import all the required dependencies:

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

Here’s why each import matters:

  • React → Provides the component structure and JSX rendering.
  • useForm from React Hook Form → Handles form registration, validation, and submission logic.
  • z from Zod → Used to define a schema that describes your form’s data shape and validation rules.
  • zodResolver → Connects your Zod schema directly with React Hook Form’s validation pipeline.

Best Practice: Separate Schema Files

It’s a good habit to keep your validation schemas in a dedicated folder (e.g., src/schemas/loginSchema.ts). This separation improves reusability, readability, and scalability — especially when multiple forms share similar validation logic.

By organizing your project this way, you can easily import and maintain your Zod schemas across different forms without cluttering your component files.

Defining the Zod Schema

Next, let’s define a validation schema for our form using Zod v4 Framework. This schema enforces the structure and validation rules for each input field.

Create a new file named:

src/schemas/loginSchema.ts

Then add the following:

import { z } from "zod";

export const loginSchema = z.object({
  email: z
    .string()
    .min(1, "Email is required")
    .email("Please enter a valid email address"),

  password: z
    .string()
    .min(8, "Password must be at least 8 characters")
    .max(32, "Password cannot exceed 32 characters")
    .regex(/[A-Z]/, "Must include at least one uppercase letter")
    .regex(/[0-9]/, "Must include at least one number")
    .refine((val) => !val.includes(" "), {
      message: "Password should not contain spaces",
    }),
});

How It Works:

  • z.string() → Ensures the field is a string.
  • .min() / .max() → Enforces length constraints for better security and UX.
  • .regex() → Adds custom pattern-based validation (e.g., checking for uppercase or digits).
  • .refine() → Enables advanced, custom logic such as disallowing spaces or validating multiple fields together.

Each method provides type inference, meaning your TypeScript types will automatically match your schema’s structure — no need for manual typing.

Understand all string-based validations and patterns: Zod String Notes

Creating the Form with React Hook Form (RHF)

Now that your schema is ready, let’s connect it to React Hook Form using the zodResolver. This allows automatic validation powered by Zod every time a user interacts with your form.

Open your LoginForm.tsx file and set up the form like this:

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

type LoginFormData = z.infer<typeof loginSchema>;

export const LoginForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = (data: LoginFormData) => {
    console.log("Form Submitted:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Email</label>
        <input type="email" {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>

      <div>
        <label>Password</label>
        <input type="password" {...register("password")} />
        {errors.password && <p>{errors.password.message}</p>}
      </div>

      <button type="submit">Login</button>
    </form>
  );
};
  • useForm() → Initializes the form and returns key utilities such as register, handleSubmit, and formState.
  • zodResolver(loginSchema) → Connects your Zod schema to RHF so that validation runs automatically based on your schema rules.
  • register() → Links each input field to the RHF state management system.
  • formState.errors → Contains validation errors mapped to each field, allowing you to display them directly in the UI.
  • z.infer<typeof loginSchema> → Automatically generates a TypeScript type (LoginFormData) that exactly matches your schema — ensuring type safety for all form operations.

With this setup, your form achieves instant validation, automatic type inference, and minimal boilerplate — all while maintaining excellent performance.

If your form includes nested objects or array fields, RHF supports deep access using dot notation or array indexes.

Displaying Error Messages in React Hook Form

Displaying validation errors clearly helps users correct mistakes quickly and improves overall UX. React Hook Form makes this simple through its built-in formState.errors object — which maps directly to your Zod schema fields.

For better UX, display error messages with clear visibility but minimal distraction. You can also integrate design libraries like Tailwind CSS or Chakra UI for consistent and accessible styling patterns.

Summary

In this guide, you’ve learned how to combine Zod v4 with React Hook Form v7 to build modern, type-safe forms in React. By leveraging Zod schemas, you can define your form data structure, enforce robust validation rules, and benefit from full TypeScript integration.

React Hook Form handles the form state efficiently, with minimal re-renders and reduced boilerplate, while zodResolver seamlessly connects Zod validation to your form. You saw how to:

  • Set up a React project with react-hook-form, zod, and @hookform/resolvers.
  • Organize your code by separating form components and Zod schemas for better maintainability.
  • Define comprehensive validation rules using .min(), .max(), .regex(), and .refine().
  • Create a fully functional form component with useForm(), register(), and formState.errors.
  • Display field-level errors, including handling nested objects or array fields, with clean and accessible styling.

By following these steps, you now have a scalable, type-safe form that’s ready for production, providing both developers and users a reliable and smooth form experience.

This foundation can be expanded to handle dynamic fields, conditional inputs, and advanced validations, making it a powerful pattern for building any form-driven React application.

Sharukhan Avatar

Sharukhan Patan

Sharukhan Patan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.

Popular Posts