Zod in React/React Native
Zod is a TypeScript-first schema validation library that helps developers define and validate the shape of data, ensuring that it adheres to the expected format. In the context of a React application, Zod is used to create schemas that define the structure of data, such as form inputs or API responses. Zod allows for strict type validation, and its close integration with TypeScript makes it possible to infer types directly from schemas, making it easier to maintain type safety across your application.
With Zod, you can define individual properties in a schema (e.g., strings, numbers, booleans) and validate them by parsing data against the schema. If the data doesn’t meet the specified criteria, Zod throws detailed errors or allows for safe parsing, returning a success/failure result for handling. This is particularly useful for validating forms, API request payloads, or environment variables.
Here’s a detailed breakdown of key concepts and examples:
1. Creating Schemas
A schema in Zod defines the structure of your data. You specify what type each field is supposed to have (e.g., string, number, array, etc.).
Example: Creating a Simple Schema
import { z } from 'zod';
const userSchema = z.object({
firstName: z.string(),
age: z.number(),
email: z.string().email(),
});
// Validating data
const userData = {
firstName: 'John',
age: 25,
email: 'john@example.com',
};
userSchema.parse(userData); // If the data is valid, it passes; otherwise, throws an error.
In this example:
z.string()
specifies thatfirstName
andemail
must be strings..email()
adds a check to ensure theemail
string is in a valid email format.z.number()
ensures thatage
is a number.
If any of these fields are missing or of the wrong type, Zod throws an error.
2. Validating Data
Once you have a schema, you can validate data by calling parse
on your schema. If the data doesn't conform, Zod throws an error.
Example: Handling Errors
try {
userSchema.parse({
firstName: 'John',
age: '25', // Wrong data type
email: 'john@example',
});
} catch (e) {
console.log(e.errors);
}
// Output: Error: expected number, got string
3. Safe Parsing
If you want to avoid exceptions and handle validation failures gracefully, use safeParse
, which returns an object with a success
property.
Example: Safe Parsing
const result = userSchema.safeParse({
firstName: 'John',
age: 25,
email: 'not-an-email',
});
if (!result.success) {
console.log(result.error.errors); // Handle the error
} else {
console.log(result.data); // Process valid data
}
4. Optional and Nullable Fields
Zod allows fields to be optional or nullable, meaning they can be absent or null
, respectively.
Example: Optional and Nullable Fields
const schema = z.object({
middleName: z.string().optional(), // Field is optional
nickname: z.string().nullable(), // Field can be null
});
schema.parse({
middleName: 'Lee', // This can be omitted, and no error will be thrown.
nickname: null, // Null is accepted here.
});
5. Chaining Modifiers
Zod allows you to chain methods to add extra validation rules. You can chain things like .min()
or .max()
for numeric or array fields, or even .email()
for strings.
Example: Adding Constraints
const schema = z.object({
age: z.number().min(18).max(60), // Age must be between 18 and 60
friends: z.array(z.string()).max(5), // Max 5 friends
});
schema.parse({
age: 25,
friends: ['Alice', 'Bob'],
});
6. Array and Object Validation
You can validate arrays and nested objects within a schema.
Example: Validating Arrays and Nested Objects
const schema = z.object({
name: z.string(),
friends: z.array(z.object({
name: z.string(),
age: z.number(),
})),
});
schema.parse({
name: 'John',
friends: [
{ name: 'Alice', age: 24 },
{ name: 'Bob', age: 30 },
],
});
In this example, the friends
field is an array of objects where each object has a name
and age
.
7. Inferring Types with TypeScript
Zod allows you to infer TypeScript types from your schema, ensuring your data is fully typed throughout your application.
Example: Type Inference
const userSchema = z.object({
firstName: z.string(),
age: z.number(),
});
type User = z.infer<typeof userSchema>;
const user: User = {
firstName: 'John',
age: 25,
};
Here, the User
type is automatically generated from the Zod schema, and you can use it throughout your codebase for type safety.
8. Zod with Forms (React Hook Form Example)
One common use of Zod is validating forms in React. You can integrate Zod with form libraries like React Hook Form to automatically validate form data.
Example: React Hook Form Integration
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const formSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email"),
});
export default function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(formSchema),
});
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} />
<p>{errors.name?.message}</p>
<input {...register("email")} />
<p>{errors.email?.message}</p>
<button type="submit">Submit</button>
</form>
);
}
In this example, Zod handles validation, and React Hook Form integrates it into the form submission process, displaying error messages if validation fails.
9. Validating Environment Variables
You can use Zod to validate environment variables to ensure your application has all the required settings at runtime.
Example: Environment Variable Validation
import { z } from 'zod';
const envSchema = z.object({
SECRET_KEY: z.string(),
PORT: z.string().regex(/^\d+$/, "Must be a number"),
});
const env = envSchema.parse(process.env);
console.log(env.SECRET_KEY, env.PORT);
Here, Zod ensures that the SECRET_KEY
and PORT
environment variables are present and valid. If they’re not, Zod will throw an error, and the application won’t run without proper configuration.
10. Error Messages
Zod allows you to customize error messages for more detailed validation feedback.
Example: Custom Error Messages
const schema = z.object({
age: z.number().min(18, "You must be at least 18 years old"),
});
try {
schema.parse({ age: 16 });
} catch (e) {
console.log(e.errors); // Output: You must be at least 18 years old
}
11. Safe Error Handling
When using .safeParse()
, instead of throwing errors, Zod returns a result object with success or failure information, allowing you to handle errors more gracefully.
Example: Safe Parsing with Error Handling
const result = schema.safeParse({ age: 16 });
if (!result.success) {
console.log(result.error.errors); // Output: Custom error message
}
Zod’s flexibility and integration with TypeScript make it an ideal tool for validating data in a wide range of contexts, from form inputs to API requests and beyond. It simplifies validation while keeping your code fully type-safe, helping prevent runtime errors.
see part 2 for Zod with Hook Forms Example.