Stop writing fragile manual validation logic. Learn how to use Zod to define declarative, type-safe schemas that validate, parse, and sanitize your form data.
Previously in this course, we covered real-time form validation using manual state checks. While that works for simple inputs, it quickly becomes unmanageable as forms grow. Today, we’re moving to a declarative approach with Zod to bring true type safety to our inputs.
When we handle user input, we are essentially performing a "handshake" between the untrusted outside world and our type-safe application logic. Manual if/else checks for every field are prone to errors and drift.
Zod allows us to define a schema—a single source of truth that describes the shape of our data and the rules for its validity. By using Zod, we get:
If you’ve explored TypeScript Zod Schema Validation: A Guide to Runtime Type Safety, you know that Zod is the industry standard for bridging the gap between raw JSON and typed objects.
Let's define a schema for our dashboard's "Profile Update" form. We need to ensure the username is at least 3 characters and the email is valid.
TYPESCRIPTimport { z } from CE9178">'zod'; const profileSchema = z.object({ username: z.string().min(3, "Username must be at least 3 characters"), email: z.string().email("Invalid email address"), age: z.number().optional(), }); // Automatically infer the type from the schema type ProfileData = z.infer<typeof profileSchema>;
By defining the schema this way, we eliminate the need to manually maintain a TypeScript interface that might fall out of sync with our validation logic.
In React, we typically want to validate data right before submission. Zod’s .safeParse() method is perfect for this because it doesn't throw errors—it returns an object containing either the success result or the validation issues.
TSXconst handleSubmit = (formData: any) => { const result = profileSchema.safeParse(formData); if (!result.success) { // result.error contains detailed issues console.error(result.error.format()); return; } // If successful, result.data is strictly typed as ProfileData const validData = result.data; console.log("Validated and sanitized data:", validData); };
One of the most powerful features of Zod is the ability to transform input. For example, if you want to ensure the username is always trimmed of whitespace and converted to lowercase, you can use .preprocess() or .transform():
TYPESCRIPTconst usernameSchema = z.string().trim().toLowerCase().min(3);
This ensures that by the time your code touches the data, it has already been cleaned. You aren't just checking if the input is valid; you are shaping it into the format your backend expects. For more advanced domain-specific logic, you might also look at TypeScript intersection types and branded types for domain validation to further harden your data models.
npm install zod.schemas/settings.ts.theme (must be either 'light' or 'dark').notifications (a boolean).newsletterFrequency (a number between 1 and 7)..safeParse() on the form data before calling your update function.safeParse: Using .parse() will throw a runtime error if validation fails, crashing your application flow if you aren't wrapping it in a try/catch. Always prefer safeParse for UI interactions..optional() or .nullable() for fields that aren't strictly required.We’ve moved from imperative, manual validation to a declarative schema-based approach. By using Zod, we’ve unified our type definitions and validation logic, ensuring that our application state remains consistent and predictable. This pattern is essential as we move toward more complex structured output: Implementing Deterministic JSON Schema Validation in later modules.
Up next: We will tackle the complexity of multi-step forms and how to persist our validated state across navigation transitions.
Learn to build an interactive search bar in React. Master controlled inputs by synchronizing form values with local state for a reactive, responsive UI.
Read moreMaster form handling in React by understanding the trade-offs between controlled components and uncontrolled components. Learn when to use state versus refs.
Schema-based Validation with Zod
Finalizing Dashboard Data Flow
Deploying the Application
Advanced Hook Composition
Implementing Middleware for State
Advanced Context Patterns
Router Loaders and Data Prefetching
Complex Route Guards
Handling Large Datasets in UI
Testing Hooks and Components
Managing Global Modals
Implementing Keyboard Shortcuts
Optimizing Asset Loading
Internationalization Basics
Managing WebSocket Connections