Carlos Dubón

Using Zod with react-hook-form and TypeScript in React

Oct 21, 2024

Cover image for the "Using Zod with react-hook-form and TypeScript in React" blog post.

If your app interacts with an API, it's crucial to validate inputs before making any requests. Imagine a sign-up form with fields for email and password. If the user types "HelloWorld" in the email field, that’s clearly not an email.

While your API might catch it and return an error like "The email must be valid," it’s more efficient to validate on both the frontend and backend. This saves time by catching errors before making unnecessary requests. So, let’s dive into how to set up validation using Zod and React Hook Form.

Step 1: Install Zod and React Hook Form

First, install the necessary packages:

yarn add react-hook-form zod @hookform/resolvers

Step 2: Define a validation schema with Zod

Now, we’ll create a schema to define our form’s structure and validation rules:

import { z } from "zod";
 
const SignUpSchema = z.object({
  email: z.string().email(),
  password: z.string().min(3).max(20),
});
 
type SignUpSchemaType = z.infer<typeof SignUpSchema>;

Here, we’re saying the email must be a string and a valid email format, and password must be a string between 3 and 20 characters.

Step 3: Set up the form component

Let’s create a basic form with fields for email and password:

export default function App() {
  return (
    <form className="form">
      <input className="input" placeholder="email" />
      <input className="input" placeholder="password" />
      <button type="submit">Submit</button>
    </form>
  );
}

Step 4: Add React Hook Form

Now we’ll integrate useForm from React Hook Form:

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
 
export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SignUpSchemaType>({
    resolver: zodResolver(SignUpSchema),
  });
 
  return (
    <form className="form">
      <input className="input" placeholder="email" {...register("email")} />
      <input
        className="input"
        placeholder="password"
        {...register("password")}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

We’ve added useForm to handle form validation using zodResolver to connect our Zod schema.

Step 5: Display validation errors

Next, we’ll show error messages below the inputs if validation fails. For example, if someone enters "HelloWorld" as their email, it’ll trigger an error:

return (
  <form className="form">
    <input className="input" placeholder="email" {...register("email")} />
    {errors.email && <span>{errors.email.message}</span>}
 
    <input className="input" placeholder="password" {...register("password")} />
    {errors.password && <span>{errors.password.message}</span>}
 
    <button type="submit">Submit</button>
  </form>
);

Now, when there’s an error in either the email or password field, the relevant error message will display.

Step 6: Handling form submission

Finally, we handle form submission with validation. When the form passes all checks, the onSubmit function will execute:

const onSubmit: SubmitHandler<SignUpSchemaType> = (data) => {
  console.log(data);
};
 
<form onSubmit={handleSubmit(onSubmit)} className="form">
  {/* form fields */}
</form>;

Full example

Here’s the complete code for reference:

import { z } from "zod";
import { useForm, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
 
const SignUpSchema = z.object({
  email: z.string().email(),
  password: z.string().min(3).max(20),
});
 
type SignUpSchemaType = z.infer<typeof SignUpSchema>;
 
export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SignUpSchemaType>({
    resolver: zodResolver(SignUpSchema),
  });
 
  const onSubmit: SubmitHandler<SignUpSchemaType> = (data) => {
    console.log(data);
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)} className="form">
      <input className="input" placeholder="email" {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
 
      <input
        className="input"
        placeholder="password"
        {...register("password")}
      />
      {errors.password && <span>{errors.password.message}</span>}
 
      <button type="submit">Submit</button>
    </form>
  );
}

Conclusion

That’s it! With Zod and React Hook Form, you can easily set up form validation in your React applications. For more advanced features, check out their documentation. Thanks for reading!