How to Use Zod with React
Practical guide to Zod in React apps — form validation, React Hook Form integration, API response validation, and TypeScript type inference.
1. Install Zod
npm install zod
# or
yarn add zod2. Define a Schema and Infer the TypeScript Type
Create a schema once — Zod infers the TypeScript type automatically:
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
age: z.number().int().min(18, "Must be 18 or older").optional(),
});
// TypeScript type inferred from schema
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }3. Validate Data with safeParse
Use safeParse to validate without throwing — returns a success/error result:
const data = { name: "Alice", email: "alice@example.com" };
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data is fully typed as User
console.log(result.data.name);
} else {
// result.error.flatten() gives field-level errors
const errors = result.error.flatten();
console.log(errors.fieldErrors);
// { email: ["Invalid email address"] }
}4. Validate API Responses in React
Parse API responses to ensure they match your expected shape:
import { useState, useEffect } from 'react';
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
userId: z.number(),
});
type Post = z.infer<typeof PostSchema>;
function usePost(id: number) {
const [post, setPost] = useState<Post | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(r => r.json())
.then(data => {
const result = PostSchema.safeParse(data);
if (result.success) {
setPost(result.data);
} else {
setError("API response did not match expected shape");
console.error(result.error.flatten());
}
});
}, [id]);
return { post, error };
}5. React Hook Form + Zod (Recommended)
Install the resolver:
npm install react-hook-form @hookform/resolversUse the Zod resolver in your form:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const LoginSchema = z.object({
email: z.string().email("Enter a valid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
type LoginData = z.infer<typeof LoginSchema>;
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginData>({
resolver: zodResolver(LoginSchema),
});
const onSubmit = (data: LoginData) => {
// data is typed as LoginData — no manual type assertion needed
console.log(data.email, data.password);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} type="email" placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("password")} type="password" placeholder="Password" />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Login</button>
</form>
);
}6. Environment Variable Validation
Validate environment variables at startup so missing vars fail fast:
import { z } from 'zod';
const EnvSchema = z.object({
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_KEY: z.string().startsWith("pk_"),
DATABASE_URL: z.string().min(1),
});
// Throws at startup if env vars are missing or invalid
const env = EnvSchema.parse(process.env);
export default env;7. Reusable Field Schemas
Extract common fields to avoid repetition:
import { z } from 'zod';
// Reusable building blocks
const emailField = z.string().email("Invalid email");
const passwordField = z.string()
.min(8, "At least 8 characters")
.regex(/[A-Z]/, "Must contain an uppercase letter")
.regex(/[0-9]/, "Must contain a number");
const LoginSchema = z.object({
email: emailField,
password: passwordField,
});
const RegisterSchema = z.object({
email: emailField,
password: passwordField,
confirmPassword: passwordField,
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});Generate a Zod schema from your JSON data automatically?
Use the JSON to Zod Converter →