Handling Forms in React Using React Hook Form ZOD and TypeScript
One major part of dealing with forms as a developer is validating the different inputs you have. When dealing with simple forms, things are generally manageable. But as your form gets more complex with more inputs and you need to add various validations, it becomes a complicated task.
Instead of writing all the logic and validation rules in your forms manually, we can make use of libraries such as react-hook-form
or formik
. In this post, we are going to look at handling and validating forms in a React application using react-hook-form
What is React Hook Form
React Hook Form is a library that helps you validate forms in React. It is a minimal library without any other dependencies while being performant and straightforward to use, requiring developers to write fewer lines of code than other form libraries.
It takes a slightly different approach than other form libraries in the React ecosystem by adopting the use of uncontrolled inputs using ref
instead of depending on the state to control the inputs. This approach makes the forms more performant and reduces the number of re-renders.
To install React Hook Form, run the following command:
npm install react-hook-form
Check my previous blog here to read more on handling forms in React without using a library
What is ZOD
Zod is a JavaScript validation library that allows you to define schemas that model the shape and constraints of your data. These schemas generate type safety, automatic validation, and useful error messages. Some key aspects of Zod include:
Validation of data types, strings, numbers, objects etc.
Customizable error messages
Support for nested objects and arrays
Code auto-completion when using a Zod schema
Type safety with TypeScript integration
By modeling application data with Zod schemas, you get built-in validation with helpful errors. It makes it easy to reuse validation logic across your application.
To work with Zod in React, you need to install the library and a resolver
npm install @hookform/resolvers zod
Setting Up Our Form
- Import the required components and hooks:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
Define your form schema using Zod:
Zod allows us to define a schema that lays out the exact shape and constraints we want to apply to our form data. Let's create one for our form:
export const createUserSchema = z.object({ name: z.string().min(2, { message: 'Name is required' }), email: z.string().email('Must be a valid email'), age: z.number().positive().int(), });
This schema defines the fields we expect along with constraints like minimum length, email validation, etc. Zod will automatically generate useful error messages for us when validation fails.
Create form component:
Using the schema we created above, we can create form with corresponding inputs as illustrated below.
type FormData = z.infer<typeof createUserSchema>; function UserForm() { const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(createUserSchema), }); const onSubmit = (data: FormData) => { console.log(data); // Handle form submission }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label htmlFor="name">Name</label> <input id="name" {...register('name')} /> {errors.name && <span>{errors.name.message}</span>} </div> <div> <label htmlFor="email">Email</label> <input id="email" {...register('email')} /> {errors.email && <span>{errors.email.message}</span>} </div> <div> <label htmlFor="age">Age</label> <input id="age" type="number" {...register('age', { valueAsNumber: true })} /> {errors.age && <span>{errors.age.message}</span>} </div> <button type="submit">Submit</button> </form> ); }
Here is what's happening:
We use
z.infer<typeof createUserSchema>
to infer the TypeScript type from our Zod schema.The
useForm
hook is initialized with the zodResolver, which connects React Hook Form with our Zod schema.We destructure
register
,handleSubmit
, anderrors
from theuseForm
hook.The
register
function is used to register our inputs with React Hook Form.We display error messages conditionally based on the
errors
object.We use the
handleSubmit
method connect to our ownonSubmit
method to handle form submission. TheonSubmit
method will be called when all validation passes and will receive the form data which you can handle as needed such as submitting it to an api.
Advanced Usage:
React Hook Form and Zod offer more advanced features that you can explore:
Custom error messages
Conditional fields
Array fields
Async validation
Here's an example of a more complex schema with custom error messages:
const advancedSchema = z.object({
username: z.string()
.min(3, { message: 'Username must be at least 3 characters long' })
.max(20, { message: 'Username cannot exceed 20 characters' }),
password: z.string()
.min(8, { message: 'Password must be at least 8 characters long' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain at least one uppercase letter, one lowercase letter, and one number'
}),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
Conclusion
Using React Hook Form with Zod provides a powerful and type-safe way to handle form validation in React applications. This combination offers several benefits:
Reduced boilerplate code
Improved performance with uncontrolled components
Strong typing and IntelliSense support
Flexible and extensible validation rules
By leveraging these libraries, you can create robust forms with complex validation logic while maintaining clean and maintainable code. As your forms grow in complexity, these tools will help you manage that complexity efficiently.
Remember to always refer to the official documentation of React Hook Form and Zod for the most up-to-date information and advanced usage scenarios.
Keep coding :)
🤖
Subscribe to my newsletter
Read articles from Martin Mwangi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Martin Mwangi
Martin Mwangi
Hello, World :). I am Martin, a software developer. I am passionate about web development and my favorite stack is React.js and Node.js. When I am not coding I love reading books, and articles and exploring the internet (mostly dribbble).