React  

Build a Validated React Login Form Using Zod and React Hook Form

When building a login form (or any kind of form) in React, it's important to manage form state, handle validation, and display errors effectively. Using React Hook Form together with Zod can make this process simple, efficient, and secure. In this article, we’ll walk through how to implement a login form using these tools to better understand this approach.

Why Use React Hook Form and Zod?

React Hook Form is a popular library for managing forms using hooks. It offers performant, easy-to-use form handling with minimal re-renders.

Zod is a TypeScript-first schema declaration and validation library. By using it, we can validate the data that will be submitted.

making it easier to handle errors.

Using both together in a React form provides a seamless, type-safe way to build forms with powerful validation.

Prerequisites

To build a React form using Zod and React Hook Form, we need to install the following packages.

npm install react-hook-form zod @hookform/resolvers

We install @hookform/resolvers because it connects the Zod schema to React Hook Form.

After installing these dependencies, we can start building the login form. Below is an example of a login form.

# App.js
import React from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import './App.css';

const loginSchema = z.object({
  email: z.string().email("Invalid email address"),
  password: z.string().min(6, "Password must be at least 6 characters"),
});

export default function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = (data) => {
    console.log("Login Data:", data);
  };

  return (
    <div className="login-container">
      <form onSubmit={handleSubmit(onSubmit)} className="login-form" noValidate>
        <h2>Login</h2>

        <div className="form-group">
          <label>Email</label>
          <input type="email" {...register("email")} />
          {errors.email && <p className="error-text">{errors.email.message}</p>}
        </div>

        <div className="form-group">
          <label>Password</label>
          <input type="password" {...register("password")} />
          {errors.password && <p className="error-text">{errors.password.message}</p>}
        </div>

        <button type="submit" className="submit-btn">Login</button>
      </form>
    </div>
  );
}

In this code snippet, after importing the necessary dependencies, we use a loginSchema defined using Zod. This schema ensures that the email must be valid and the password must be at least six characters long.

We use the resolver with useForm to connect the schema. All validation logic is now handled automatically based on the defined schema.

Each input field is registered using register("fieldName"), and any validation errors are displayed directly below the corresponding field.

Once the form is valid and submitted, the onSubmit function receives the form data and logs it to the console.

To add styling, include the following code in your App.css file.

  
    # App.css
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: #f4f6f8;
}

.login-form {
  background: white;
  padding: 2rem 3rem;
  border-radius: 8px;
  box-shadow: 0 0 15px rgba(0,0,0,0.1);
  width: 100%;
  max-width: 400px;
}

.login-form h2 {
  text-align: center;
  margin-bottom: 1.5rem;
}

.form-group {
  margin-bottom: 1rem;
}

label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

input {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.error-text {
  color: red;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.submit-btn {
  width: 100%;
  padding: 0.75rem;
  background-color: #007bff;
  color: white;
  border: none;
  font-weight: bold;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 1rem;
}

.submit-btn:hover {
  background-color: #0056b3;
}

After running this code, you will see the output as shown in the image below. It also displays error messages when invalid input is submitted.

error messages

By using this approach, we can handle errors easily, as they are clearly displayed below each input field. All validation rules are defined in one place using the Zod schema, making the code more organized and maintainable. Additionally, by using React Hook Form, the form is optimized for better performance through minimal re-renders.

Conclusion

Combining React Hook Form and Zod offers a powerful, clean, and scalable approach to building and validating forms in React. Whether you're creating a simple login page or a complex, multi-step form, this setup helps keep your code maintainable and your user experience smooth and professional.