← All Blogs

Handling Form Validation Using Zod, React-hook-form, and Typescript

Learn how to validate your applications forms using latest tools like Zod, react-hook-form, and Typescript with React

Published on - by Jacob Kyalo

Introduction

Forms are very fundamental when building web applications. Actually any web application contains one or more forms. These forms are used to collect user data and sent it to the server or store data in a database.

Therefore, validating forms before user sent data to the database is very crucial process to ensure that data collected from users is accurate and complete thus preventing malicious users from submitting harmful data or code. Let's dive in.

This article assumes that you have a basic understanding of what react is and how to create react components. If not check react docs for more information.

What to learn

By the end of this article, you will have a better understanding of the following concepts:

  • How to create a react application with Vite and Typescript
  • What is Zod, React-hook-form, and Typescript
  • How to create form schemas
  • How to style react components using TailwindCSS
  • How to display form errors

Creating a react application with Vite and Typescript

To follow around, you will have to create a new react application using Vite and Typescript. Navigate to a directory where you want to create your react app and run the following command in your command line or terminal

npm create vite@latest form-validation

This command will scalfold react-vite application called form-validation with typescript. Make sure to choose React and Typescript from the terminal prompts. Now change directory to the newly created app by:

cd form-validation

You will also need to install the dependencies from the scalfolded application using this command

npm install // or npm i

Setting up TailwindCSS

TailwindCSS is utility-first CSS framework packed with classes that can be composed to build any design, directly in your markup (HTML). To install and configure Tailwind CSS in your vite project. Install Tailwind CSS and its dependencies by running this command in your terminal

npm install -D tailwindcss postcss autoprefixer

Initialize TailwindCSS Configuration by running this command in your terminal

npx tailwindcss init -p

This command will create a tailwind.config.js file and a postcss.config.js file in your project root. Open tailwind.config.js and configure the content array to include your template files by pasting the following code

/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

To finish setting up TailwindCSS, you need to add Tailwind directives to root CSS file that is src/index.css and paste the following code. Also, delete the src/App.css file to remove the default styles.

@tailwind base;
@tailwind components;
@tailwind utilities;

Creating a form

For this article, we will create a login form with a email and password fields. For simplicity we will put all our code in the App.tsx file. Delete the code in App.tsx and paste the following code

import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";

const schema = z.object({
  email: z.string().email({
    message: "Please enter a valid email",
  }),
  password: z.string().min(8, {
    message: "Password must be at least 8 characters",
  }),
});

type FormSchema = z.infer<typeof schema>;

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm <
  FormSchema >
  {
    resolver: zodResolver(schema),
    defaultValues: {
      email: "",
      password: "",
    },
  };

  const onSubmit = (data: FormSchema) => {
    console.log(data);
  };

  return (
    <main className="h-screen grid place-items-center">
      <form
        onSubmit={handleSubmit(onSubmit)}
        className="w-full max-w-xl bg-white shadow-lg border p-8 rounded-lg"
      >
        <div className="mb-4">
          <label htmlFor="email" className="block mb-2">
            Email adress
          </label>
          <input
            id="email"
            type="email"
            {...register("email")}
            placeholder="Email address"
            className="w-full outline-none p-3 border focus:border-2 focus:border-blue-600 rounded-lg"
          />
          {errors.email && (
            <p className="text-sm text-red-600 mt-1">{errors.email.message}</p>
          )}
        </div>

        <div className="mb-4">
          <label htmlFor="password" className="block mb-2">
            Password
          </label>
          <input
            id="password"
            type="password"
            {...register("password")}
            placeholder="Password"
            className="w-full outline-none p-3 border focus:border-2 focus:border-blue-600 rounded-lg"
          />
          {errors.password && (
            <p className="text-sm text-red-600 mt-1">
              {errors.password.message}
            </p>
          )}
        </div>
        <button
          className="w-full rounded-lg p-3 bg-blue-600 text-white"
          type="submit"
        >
          Login
        </button>
      </form>
    </main>
  );
}

Run the following command on your terminal to start the development server

npm run dev

You should see the following interface on your browser if all goes well

login-form

That's alot. Let me explain. First of we are importing zod like z form zod. Zod is a TypeScript-first schema validation with static type inference. It helps define schemas for form validations.

Then we are defining a type for our form schema like this

type FormSchema = z.infer<typeof schema>;

Inside the App component in App.tsx file, we are using useForm from react-hook-form with zodResolver to validate the form. React-hook-form is a performant, flexible and extensible form validation library.

From the useForm hook, we are destructuring three things:

  • The register method that is a powerful utility that connects our input elements (email & password) to the library, enabling seamless form state management and validation.
  • The handleSubmit method is used to handle form submission
  • The formState object is used to handle form validation errors

Note: Each field is required to have a name as a key for the registration process to happen and react-hook-form to know which form field to validate

We are assigning the FormShema type to the useForm hook. We are also setting the resolver to zodResolver that comes from the @hookform/resolvers package. Finally, we are setting the defaultValues of input fields (email & password) to empty strings at first.

On the form tag, we are passing the handleSubmit method to the onSubmit event listener of the form and then the handleSubmit method takes one argument which is the onSubmit function that we just created that logs the form data to the console.

Finally, we are creating our form, registering the input fields with react-hook-form, and adding some tailwindcss utility classes to make the form look nicer.

Displaying error messages

The p tag below each input field is used to display an error message if any of the field validation fails.

//...
{
  errors.email && (
    <p className="text-sm text-red-600 mt-1">{errors.email.message}</p>
  );
}
//...
{
  errors.password && (
    <p className="text-sm text-red-600 mt-1">{errors.password.message}</p>
  );
}
//...

When the form validation fails, you should see this form errors on your browser.

login-form-error

Now, if you fill the form correctly and submit, you should see your login details on the developer console. Open developer console by pressing the F12 or Ctrl+Shift+J on Windows or Cmd+Option+J on Mac. You should see the following interface on your browser

login-form-success

Conclusion

Incorporating form validation in a React applications can be greatly simplified and enhanced with the use of Zod and React-hook-form. Zod provides a powerful, schema-based validation system that ensures your data adheres to defined rules, offering both type safety and runtime validation. Combined with react-hook-form, a library designed to provide flexible and performant form management, you can create robust forms which are more secure. This can help improve Web Security in general and minimize threats that may occur due insecure forms.

Happy coding!