Zod is a TypeScript-first validation library. It automatically infers types from schemas.
Why it matters: In React applications, data validation is needed in multiple places — form inputs, API responses, URL parameters, and localStorage parsing. Zod provides a single schema that serves as both the runtime validator and the TypeScript type, eliminating the common bug where types and validation logic diverge.
How Zod works:
schema.parse(data) to validate.1import { z } from "zod";2import { useForm } from "react-hook-form";3import { zodResolver } from "@hookform/resolvers/zod";45// 1. Define schema6const registerSchema = z.object({7 name: z.string().min(2, "Minimum 2 characters"),8 email: z.string().email("Invalid email"),9 password: z.string().min(8, "Minimum 8 characters"),10 age: z.number().min(18, "Minimum 18 years"),11 website: z.string().url("Invalid URL").optional(),12});1314// TypeScript type is inferred automatically!15type RegisterForm = z.infer<typeof registerSchema>;1617// 2. Form with Zod validation18function RegisterForm() {19 const {20 register,21 handleSubmit,22 formState: { errors },23 } = useForm<RegisterForm>({24 resolver: zodResolver(registerSchema),25 });2627 const onSubmit = async (data: RegisterForm) => {28 await fetch("/api/register", {29 method: "POST",30 body: JSON.stringify(data),31 });32 };3334 return (35 <form onSubmit={handleSubmit(onSubmit)}>36 <input {...register("name")} placeholder="Name" />37 {errors.name && <span>{errors.name.message}</span>}3839 <input {...register("email")} placeholder="Email" />40 {errors.email && <span>{errors.email.message}</span>}4142 <input {...register("password")} type="password" />43 {errors.password && <span>{errors.password.message}</span>}4445 <input {...register("age", { valueAsNumber: true })} type="number" />46 {errors.age && <span>{errors.age.message}</span>}4748 <button type="submit">Register</button>49 </form>50 );51}
Advanced Zod patterns:
1// Chaining validations2const schema = z.object({3 password: z.string().min(8).max(100).regex(/[A-Z]/, "Must contain uppercase"),4 confirm: z.string(),5}).refine(data => data.password === data.confirm, {6 message: "Passwords don't match",7 path: ["confirm"],8});910// Validating API responses11const UserSchema = z.object({ id: z.number(), name: z.string() });12const users = UserSchema.array().parse(apiResponse);
Zod advantages: