c459effa5faff4e8b369
·4 min read

Understanding Zod, A Powerful Tool for Data Validation

typescript
zod
react-hook-form
schema
react
4d5fd787ac74e0caa4f7

Sohan R. Emon

Developer, Learner, Tech Enthusiast

Zod is a TypeScript-first schema definition and validation library that's gaining popularity among React developers. Let's dive into it and everything you need to know to get started with Zod.

What is Zod?

Zod enables developers to define the shape and constraints of data using schemas. It provides robust type inference, which means you get TypeScript types for free based on your Zod schemas.

Creating a Zod Schema

A schema defines the structure and constraints of a piece of data. Here's how you create a basic schema with Zod:

typescript
import { z } from 'zod';

const personSchema = z.object({
  name: z.string(),
  age: z.number().positive(),
  email: z.string().email(),
});

Validating Data

Once you have a schema, you can validate data against it. If the data doesn't match the schema, Zod will throw an error.

typescript
const person = {
  name: 'Alice',
  age: 25,
  email: 'alice@example.com',
};

try {
  // Either succeed or throw an error
  personSchema.parse(person); // Validation success
} catch (error) {
  console.error(error); // Validation failure
}
// Doesn't throws an error, returns object
personSchema.safeParse(person); // Object

Type Inference

Zod's type inference automatically generates TypeScript types from your schemas:

typescript
type Person = z.infer<typeof personSchema>;
// Person is now { name: string; age: number; email: string; }

Primitives and Complex Types

Zod supports various primitives and complex types:

typescript
// Primitives
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();

// Complex types
const arraySchema = z.array(z.string());
const tupleSchema = z.tuple([z.string(), z.number()]);
const unionSchema = z.union([z.string(), z.number()]);

Validation Options

Zod provides options to customize the validation process:

typescript
const partialPersonSchema = personSchema.partial(); // All properties are optional

const requiredPersonSchema = personSchema.required(); // Ensure object isn't undefined

const passedPersonSchema = personSchema.passthrough(); // Ignores unrecognized keys

Combining Schemas

Zod allows you to combine schemas to create more complex ones:

typescript
const employmentSchema = z.object({
  jobTitle: z.string(),
  salary: z.number(),
});

const employeeSchema = personSchema.merge(employmentSchema);

Custom Validation

You can create custom validation logic using .refine():

typescript
const passwordSchema = z.string().refine((val) => val.length >= 8, {
  message: 'Password must be at least 8 characters',
});

Nested Schemas

You can create complex, nested schemas with Zod to validate objects within objects. This is particularly useful for validating complex data structures.

typescript
const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zipcode: z.string().length(5),
});

const userSchema = z.object({
  name: z.string(),
  address: addressSchema, // Nested schema
});

Transformations

Zod schemas can transform data as it's being validated, such as converting strings to numbers, trimming whitespace, or applying custom transformations.

typescript
const trimmedString = z.string().transform((str) => str.trim());
const stringToNumber = z.string().transform((str) => parseInt(str, 10));

Asynchronous Validation

Zod supports asynchronous validation, which is helpful for validations that require database lookups or other async operations:

typescript
const usernameSchema = z.string().refine(async (username) => {
  const exists = await checkUsernameAvailability(username); // An async function
  return !exists;
}, 'Username is already taken');

Error Handling

When validation fails, Zod provides detailed error information:

typescript
const result = personSchema.safeParse({
  name: 'Bob',
  age: -10,
  email: 'not-an-email',
});
if (!result.success) {
  console.log(result.error.errors); // Array of error details
}

Custom Error Messages

Zod allows you to define custom error messages for each validation rule, making it easier for users to understand what went wrong.

typescript
const loginSchema = z.object({
  username: z.string().min(1, 'Username cannot be empty'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

Using Zod with React-Hook-Form: Advanced

When integrating Zod with react-hook-form, you can take advantage of advanced features like conditional validation and using external validation APIs.

typescript
import { zodResolver } from '@hookform/resolvers/zod';
import { Controller, useForm } from 'react-hook-form';

const formSchema = z.object({
  accountType: z.enum(['personal', 'business']),
  businessName: z
    .string()
    .optional()
    .refine((name, context) => {
      // Only validate if accountType is 'business'
      if (context.parent.accountType === 'business') {
        return name?.trim().length > 0;
      }
      return true;
    }, 'Business name is required for business accounts'),
});

type FormInputs = z.infer<typeof formSchema>;

const {
  register,
  handleSubmit,
  watch,
  formState: { errors },
} = useForm<FormInputs>({
  resolver: zodResolver(formSchema),
});

const MyForm = () => {
  const accountType = watch('accountType');

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <select {...register('accountType')}>
        <option value="personal">Personal</option>
        <option value="business">Business</option>
      </select>

      {accountType === 'business' && <input {...register('businessName')} />}
      {errors.businessName && <p>{errors.businessName.message}</p>}

      <button type="submit">Submit</button>
    </form>
  );
};

Conclusion

Zod is a powerful tool for any React developer looking to enforce data integrity with a simple, yet powerful API. Start integrating Zod into your next projects today and experience the benefits of a well-validated application.

Found this useful?