Published on January 30, 2025
Form validation is an integral part of applications that collect data from users.
Forms that authenticate users, collect addresses for shipping, allow users to select booking dates, and anything else you can think of, should all verify that the data they collect is complete, accurate, and in the correct format. Form validation helps to maintain security, keeps data consistent, and provides feedback to users so they can correct information before submitting it, improving the user experience.
This post demonstrates form validation in React using Zod validation with React Hook Form, and provides full code examples demonstrating how you can use Zod validation in your own projects.
Zod is a schema validation library that has a TypeScript-first approach, offering developers an elegant way to define and enforce data structures. Zod stands out by providing a way to generate TypeScript types directly from its schemas, so that you don't need to write identical but redundant type definitions for different purposes. Zod also provides runtime type safety in JavaScript projects without the need for TypeScript, making it useful for a wide range of projects.
What sets Zod apart from other libraries is its extensive API, which mirrors TypeScript's capabilities for type inference and manipulation, making it great for larger, more complex applications. In React, Zod schemas can be used to validate form data and provide error handling, improving user experience.
When building forms for websites and apps, it's best practice to guarantee type safety and the correct format for collected data to stop users from submitting incorrect or incomplete data. This prevents errors both at runtime and further down the track when you attempt to process or analyze the submitted data. You can achieve this by combining Zod's schema validation with React Hook Form.
React Hook Form is a lightweight React form library that simplifies form building and validation in React. It uses React hooks for form state management, minimizing re-renders, and improving performance. When paired with React Hook Form, Zod streamlines implementing form validation by integrating validation logic and type safety. Combined with the efficient form handling of React Hook Form, this reduces the risk of inconsistencies in validation rules.
While it is absolutely vital that your back ends validate all data (it's worth noting here that Zod can also be used in server environments, further allowing you to re-use the same schema code) to minimize any security vulnerabilities such as SQL injection, your front ends should also validate data.
Validation on the front end should never be relied on for backend and database security, as the client-side validation is trivial to bypass (as the user has control of the web browser they're using). However, implementing frontend validation prevents users getting frustrated and wasting server resources with repeated form submissions that contain invalid data that cannot be used. For example, if a user mistakenly enters an email of userexample.com
instead of user@example.com
into a registration form, it would be better to catch that on the client than to send it to the server, wasting resources to generate an error, and then finally tell the user.
Zod provides the following schema validations out of the box. These can then be used with React Hook Form to notify the user if they have made a mistake when inputting data:
Required fields: Ensure that all mandatory inputs are filled.
Data types: Confirm that values match the expected types (numbers, strings).
Value ranges: Validate that numbers and dates fall within a specified limit.
Data validity: Check that URLs, emails, or phone numbers adhere to the correct format.
Length constraints: Enforce minimum and maximum character limits for text fields.
Cross-field validation: Validate relationships between fields, such as ensuring "Confirm password" matches the original password.
Zod provides developer-friendly schema validation syntax that combines simplicity with practicality, as demonstrated in the Zod schema code examples below.
Common validation syntax: The Zod API lets you validate data structures like strings, numbers, and booleans.
Zod schemas: Schemas define the expected shape and constraints of data objects. Schemas form the foundation for all Zod validations — for example, the userSchema
below defines a valid user data type, including its properties.
Error message customization: You can provide custom error messages for when what the user enters doesn't match your schema requirements, and these can then be obtained and displayed with React Hook Form.
Validating objects with Zod schemas: Once you have defined a schema you can parse your data through it to make sure it meets the requirements.
Validating nested objects and arrays: Zod validation supports data structures like objects and arrays, allowing you to validate multiple nested objects.
In the above example, userSchema
defines a validation for a nested user object, and highScoresSchema
defines an array where each element has a user
field validated with the userSchema
and a score
field which must be a positive number. The example data is defined and then validated using the highScoresSchema
.
Custom validation: Zod allows you to define custom validation logic with the .refine
function for use cases that aren't met with its included functionality. The below example validates that a VAT (sales tax) number conforms to a given specification.
Zod schema derivation: You can extend schemas from your existing ones to promote code reusability.
The extendedVatNumberSchema
now contains the additional refinement to ensure only digits follow “VAT” so using this schema ensures the string starts with “VAT” and is followed by digits.
Zod field value transformations: You can transform data as part of the validation process.
const trimmedString = z.string().transform((val) => val.trim());
When validating this string the trim()
function removes any whitespace characters from the beginning and end of the string.
Data fetching and parsing in Zod validation: Zod can validate data retrieved from an external source using schema.parse
. This function validates the data against the defined schema but also throws an error if the data doesn’t conform to the defined structure.
In the above example, the schema ensures that apiResponse
contains an object with a key called data, which must be an array of strings.
TypeScript type inference: Zod can create TypeScript types by inferring them from its own schemas:
In the above example, userSchema
is a Zod schema that defines the shape of a user object, and the z.infer
utility extracts the TypeScript type from the schema definition (essentially translating the schema into a corresponding TypeScript type). The z.infer
line is equivalent to:
This tutorial will guide you through creating an invoice generator in React using Zod validation and React Hook Form for the form handling.
This Zod validation tutorial covers:
Setting up Zod and React Hook Form in your React project.
Defining schemas and integrating them with Zod and TypeScript.
Implementing complex validations, including cross-field and dynamic field validations.
Building a fully functional invoice generator that validates form data.
You can see the full working example code for this tutorial here.
You can initialize a new React TypeScript project with Vite and then install the necessary react-hook-form and zod libraries, as well as the React Hook Form resolvers that integrate external validation libraries like Zod with React Hook Form:
npm install react-hook-form zod @hookform/resolvers
Create a file named schema.ts
in the src
directory of your project and add the following code to define the schema for the invoice form:
The above example defines a Zod schema (invoiceSchema) for validating the invoice form, which specifies requirements and error messages for the fields of the form:
Customer and Address fields: Must not be empty.
Dates: Must have a valid date format.
Line items: Must include at least one item with a description, a quantity of at least 1, and a price greater than 0.01.
It then uses type inference to export the schema as a reusable InvoiceFormValues
type.
Next, build the form using React Hook Form and integrate the Zod schema by updating your src/App.tsx
file (if you created your project using Vite) to contain the following:
The TypeScript types and Zod validation for the form are defined in the useForm
function along with the default values. The useFieldArray
hook is then used to get the fields, and the append and remove functions so the user can append new lines and remove them for the invoice. Then the field is built by mapping out the field values from React Hook Form.
Notice that if you haven't filled out all values, the form won't submit but instead fails silently. That's because you need to add the error messages that were defined in the schema. To do this, update your src/App.tsx
file to contain the following:
The above code updates App.tsx
to include errors by destructuring the error object from the formState
and then checking each line if the error is present; if it is, it displays the error message text which was defined in the schema.
For example, the following validation code is added for the customerName
field:
If the errors
object has a customerName
property, the error will be displayed.
With cross-field validation, Zod can check that the due date is on or after the invoice date and display a message to the user if it’s not. To do this, replace the code in the schema.ts
file with the following:
The above code chains the refine
function on to the end of the object validation definition and makes a custom check to make sure the invoice date is not before the current date and that the due date is not before the invoice date; these errors will now be displayed to the user.
You can validate that each description for the line item in the invoice is unique using superRefine. To do this, update the schema.ts
file, adding the superRefine
function in the below snippet to the lineItems
schema.
The above code checks each description from the line items array and makes sure there are no duplicates. If validation fails, an error message is added with ctx.addIssue
.
Seeing as the front end can be completely bypassed (through malicious tampering), you need to add input validation to your back end to avoid security vulnerabilities such as SQL injection. You can use Zod validation to check inputs on the server after submission.
Here's a basic example of server-side validation using Zod:
The code example above defines a schema with Zod, then defines a function to which you can pass the incoming data to be validated, and then checks the result of the validation. In this example, there is an SQL injection attack in the name
field, so validation will fail and the database is protected.
The Contentful® Composable Content Platform lets you define your content model, including content types made up of text, images, videos, and other media, manage it in our creator-friendly user interface, and then publish using REST and GraphQL delivered from our high-speed network.
By integrating Contentful with React Hook Form and Zod for validation, you can ensure all data sent and received is clean, validated, and consistent. This combination significantly reduces the risk of runtime errors caused by invalid data, and gives your users the best possible experience when using your React apps.
Subscribe for updates
Build better digital experiences with Contentful updates direct to your inbox.