Form Handling and Validation

Introduction

Form handling and validation are critical aspects of any frontend application. Ensuring that user input is correctly formatted and meets required criteria is essential for both functionality and security. In this project, built with Next.js, TailwindCSS, and TypeScript, we use Zod, a TypeScript-first schema declaration and validation library, to manage form validation effectively.


Why Use Zod?

Zod provides a simple yet powerful way to define schemas for data validation. Unlike other libraries, it has first-class support for TypeScript, making it a great choice for a strongly-typed environment. Here are some of its key features:

  1. TypeScript Integration: Automatically infer TypeScript types from schemas.
  2. Composability: Easily combine schemas for complex validation needs.
  3. Readable Error Messages: Zod provides clear and detailed error messages for invalid inputs.

Setting Up Zod in Your Project

Installation

First, install Zod in your project:

npm install zod

Defining and Using Schemas

Creating a Schema

Define a schema for the data you want to validate. For example, a form with fields for name, email, and password:

import { z } from "zod";

const formSchema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email address"),
  password: z.string().min(6, "Password must be at least 6 characters long"),
});

Validating Form Data

Basic Validation

To validate data, use the .parse or .safeParse methods provided by Zod.

const formData = {
  name: "John Doe",
  email: "johndoe@example.com",
  password: "securepassword",
};

try {
  const validatedData = formSchema.parse(formData);
  console.log("Valid data:", validatedData);
} catch (e) {
  console.error("Validation errors:", e.errors);
}

Using .safeParse

If you prefer a non-throwing validation method, use .safeParse, which returns a success flag and result or error.

const result = formSchema.safeParse(formData);

if (result.success) {
  console.log("Valid data:", result.data);
} else {
  console.error("Validation errors:", result.error.errors);
}

Integrating Zod with Forms in Next.js

Example: A Simple Login Form

Use Zod to validate form inputs before submission.

import { useState } from "react";
import { z } from "zod";

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

export default function LoginForm() {
  const [formData, setFormData] = useState({ email: "", password: "" });
  const [errors, setErrors] = useState({ email: "", password: "" });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const result = loginSchema.safeParse(formData);

    if (!result.success) {
      const errorObj: Record<string, string> = {};
      result.error.errors.forEach((error) => {
        if (error.path[0]) errorObj[error.path[0] as string] = error.message;
      });
      setErrors(errorObj);
    } else {
      console.log("Form submitted successfully:", result.data);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
        {errors.email && <p>{errors.email}</p>}
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
        {errors.password && <p>{errors.password}</p>}
      </div>
      <button type="submit">Login</button>
    </form>
  );
}

Handling Advanced Scenarios

Nested Objects

Zod supports validation of nested objects:

const nestedSchema = z.object({
  user: z.object({
    name: z.string(),
    age: z.number().min(18, "Must be at least 18 years old"),
  }),
});

Arrays

You can also validate arrays:

const arraySchema = z.array(z.string().min(1, "String cannot be empty"));

const result = arraySchema.parse(["item1", "item2"]);
console.log(result); // ["item1", "item2"]

  • React Hook Form: Integrate Zod with React Hook Form for better form handling.
  • Zod Resolver: Use the @hookform/resolvers/zod package to easily connect Zod schemas with React Hook Form.

With Zod, you can handle and validate forms in a clean, efficient, and strongly-typed way. By leveraging its TypeScript-first approach, you ensure your forms are robust and maintainable. Happy coding!