website-logomedcode

Next.js form validation using "zod" server-action

Enter Zod and Next.js Server Actions—a formidable alliance reshaping the paradigm of form validation within the React ecosystem. 🚀 Envision a landscape where forms are seamlessly validated, errors are gracefully managed, and instantaneous feedback delights your users—all underpinned by the might of server-side processing. This isn’t a lofty aspiration; it’s a tangible reality beckoning for exploration.
MED DKR| December 24, 2024
Next.js form validation using "zod" server-action

#TypeScript #server-action #zod

Are you exasperated by the chaos of unwieldy form validations in your Next.js projects? 😟 Does managing user input on the server-side feel like an uphill battle fraught with complications? Rest assured, you’re not navigating this labyrinth alone. Countless developers grapple with these exact hurdles while striving to construct reliable web applications.

Enter Zod and Next.js Server Actions—a formidable alliance reshaping the paradigm of form validation within the React ecosystem. 🚀 Envision a landscape where forms are seamlessly validated, errors are gracefully managed, and instantaneous feedback delights your users—all underpinned by the might of server-side processing. This isn’t a lofty aspiration; it’s a tangible reality beckoning for exploration.

In this discourse, we’ll embark on an in-depth exploration of form validation in Next.js, harnessing the prowess of Zod and Server Actions. We’ll commence by unraveling the foundational principles of Next.js Server Actions and illuminating the transformative capabilities of Zod. Subsequently, we’ll escort you through configuring your project, instituting robust validation mechanisms, and enhancing efficiency. By journey’s end, you’ll be equipped with proven methodologies and insights to elevate your form-handling dexterity to unparalleled heights. Let’s delve into this transformative endeavor and pioneer a smoother, more dependable development workflow, fostering applications that users cherish.

Zod is a robust schema declaration and validation library that prioritizes TypeScript. It enables developers to craft intricate schemas for data validation effortlessly. With its intuitive and declarative API, Zod simplifies the process of defining and validating complex data structures, making it a favorite among developers.

Why Choose Zod for Form Validation?

Zod stands out as a powerful tool for form validation in Next.js applications due to its numerous advantages:

  • TypeScript Integration: Zod integrates seamlessly with TypeScript, ensuring type safety and reducing runtime errors.
  • Runtime Type Checking: It provides reliable runtime validation, ensuring data integrity even beyond TypeScript's static checks.
  • Server-Client Validation: Zod supports validations that can run both on the server and client, simplifying code reuse and consistency.
  • Customizable Error Messages: Developers can define meaningful and user-friendly error messages tailored to specific requirements.
  • Excellent Performance: Zod is designed to handle validations efficiently, making it suitable for high-performance applications.

By leveraging Zod, developers can build reliable, scalable, and user-friendly forms with ease, streamlining the development process while enhancing user experience.

1.Creating a new Next.js app

npx create-next-app@latest my-form-app

2.install zod dependency

npm install zod @conform-to/react @conform-to/zod

3. create reservationSchema.tsx

import { z } from"zod";
export const reservationSchema=z.object({
  name:z.string().min(3).max(20),
  phone:z.string().min(10).max(10),
  city:z.string().min(3).max(20),
  address:z.string().min(3).max(20),
  hour:z.string().min(3).max(20),
  type_car:z.string().min(3).max(20),
  end_date:z.string().min(3).max(20),
  start_ate:z.string().min(3).max(20),
});

4.create Reservation.tsx component

"use client";
import React, { useActionState } from "react";
import { Caveat } from "next/font/google";
import { reserveCar } from "@/src/utils/action";
import { useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { reservationSchema } from "@/src/utils/ReservationSchema";

const caveat = Caveat({
  variable: "--font-caveat",
  subsets: ["latin"],
});

const Reservation = () => {
  const [state, action, isPending] = useActionState(reserveCar, undefined);
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema: reservationSchema });
    },
    shouldValidate: "onBlur",
    shouldRevalidate: "onInput",
  });

  return (
    <div className="w-full p-16 px-16 bg-light rounded-lg shadow-md lg:p-12 md:p-8 sm:px-2 sm:p-2">
      <h2
        className={`${caveat.variable} text-3xl font-extrabold text-gray-700 mb-6 px-14 sm:px-2`}
      >
        Car Reservation
      </h2>

      <form
        id={form.id}
        onSubmit={form.onSubmit}
        action={action}
        className="w-full px-16 sm:px-2"
      >
        <div className="grid grid-cols-2 gap-x-6 md:flex md:flex-col">
          {/* Name */}
          <div className="mb-4">
            <label
              htmlFor="name"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              FULL Name
            </label>
            <input
              type="text"
              id="name"
              className="w-full border border-gray-300 rounded-lg p-2 px-5 py-3 focus:outline-none focus:ring-2 focus:ring-primary"
              placeholder="Enter your full name"
              name={fields.name.name}
              defaultValue={fields.name.initialValue}
              key={fields.name.key}
            />
            <p className="text-primary text-sm">{fields.name.errors}</p>
          </div>
          {/* Phone */}
          <div className="mb-4">
            <label
              htmlFor="phone"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              NUMBER Phone
            </label>
            <input
              type="tel"
              id="phone"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              placeholder="Enter your phone number"
            />
          </div>
          {/* Address */}
          <div className="mb-4">
            <label
              htmlFor="address"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              Address
            </label>
            <input
              type="text"
              id="address"
              name="address"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              placeholder="Enter your address"
              required
            />
          </div>
          <div className="mb-4">
            <label
              htmlFor="age"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              Age
            </label>
            <input
              type="number"
              id="age"
              name="age"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              placeholder="Enter your age"
              min={26}
              required
            />
          </div>
          {/* Date */}
          <div className="mb-4">
            <label
              htmlFor="date"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              start Reservation Date
            </label>
            <input
              type="date"
              id="date"
              name="start_date"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-primary"
              required
            />
          </div>
          <div className="mb-4">
            <label
              htmlFor="date"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              End Reservation Date
            </label>
            <input
              type="date"
              id="date"
              name="end_date"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-primary"
              required
            />
          </div>
          {/* Hour */}
          <div className="mb-4">
            <label
              htmlFor="hour"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              Reservation Hour
            </label>
            <input
              type="time"
              id="hour"
              name="hour"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              required
            />
          </div>
          {/* Select a Car */}
          <div className="mb-4">
            <label
              htmlFor="car"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              Select a Car
            </label>
            <select
              id="car"
              name="type_car"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              required
            >
              <option disabled selected>
                Select a car
              </option>
              <option value="DACIA SANDERO">DACIA SANDERO</option>
              <option value="DACIA DUSTER">DACIA DUSTER</option>
              <option value="RENAULT CLIO">RENAULT CLIO</option>
              <option value="HYDAI ACCENT">HYDAI ACCENT</option>
              <option value="PEUGOUT 3008">PEUGOUT 3008</option>
              <option value="PEUGOUT 308">PEUGOUT 308</option>
            </select>
          </div>

          <div className="mb-4">
            <label
              htmlFor="city"
              className="block text-gray-700 font-medium mb-2 uppercase"
            >
              City
            </label>
            <select
              id="city"
              name="city"
              className="w-full border border-gray-300 px-5 py-3 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary"
              required
            >
              <option disabled selected>
                Select a city
              </option>
              <option value="casablanca">Casablanca</option>
              <option value="marrakech">Marrakech</option>
              <option value="rabat">Rabat</option>
              <option value="benguerir">benguerir</option>
              <option value="fes">Fes</option>
              <option value="tangier">Tangier</option>
              <option value="agadir">Agadir</option>
              <option value="meknes">Meknes</option>
              <option value="oujda">Oujda</option>
            </select>
          </div>
        </div>
        {/* Submit Button */}
        <div>
          <button
            type="submit"
            className="w-1/2 flex justify-center bg-red-500 text-white font-semibold py-3 px-4 rounded-lg hover:bg-primary focus:outline-none focus:ring-2 focus:ring-primary"
          >
            Reserve Now
          </button>
        </div>
      </form>
    </div>
  );
};

export default Reservation;

5. create server action file action.tsx

"use server";
import connect from "@/src/utils/connect";
import Orders from "@/src/utils/Orders";
import { revalidatePath } from"next/cache";
import { redirect } from"next/navigation";
import { parseWithZod } from"@conform-to/zod";
import { reservationSchema } from"./ReservationSchema";

export const reserveCar=async (prevState:unknown, formData:FormData) => {
  constvalidation=parseWithZod(formData, {
    schema:reservationSchema,
  });
  try {
    if (validation.status!=="success") {
      returnvalidation.reply();
    }
    connect();
    const name=formData.get("name") asstring;
    const phone=formData.get("phone") asstring;
    const city=formData.get("city") asstring;
    const address=formData.get("address") asstring;
    const hour=formData.get("hour") asstring;
    const type_car=formData.get("type_car") asstring;
    const end_date=formData.get("end_date") asstring;
    const start_date=formData.get("start_date") asstring;
    const age=formData.get("age") asstring;
    const order=newOrders({
      name,
      phone,
      city,
      address,
      hour,
      type_car,
      end_date,
      start_date,
      age,
    });
    awaitorder.save();
    console.log("order is created");
  } catch (error) {
    return"something went error";
  }
  redirect("/success");
};

6. install MongoDB and mongoose:

npm i mongodb & npm i mongoose

7.create Order.js create modal of mongoDb

import mongoose from "mongoose";
const { Schema } =mongoose;

const orderSchema=new Schema(
  {
    name: {
      type:String,
      unique:true,
      required:true,
      min:3,
      max:20,
    },
    phone: {
      type:String,
      unique:true,
      required:true,
    },
    address: {
      type:String,
      required:false,
    },
    age: {
      type:String,
    },
    start_date: {
      type:String,
    },
    end_date: {
      type:String,
    },
    hour: {
      type:String,
    },
    type_car: {
      type:String,
    },
    city: {
      type:String,
    },
  },
  { timestamps:true }
);

//If the User collection does not exist create a new one.
const Orders=mongoose.models?.orders||mongoose.model("orders", orderSchema);
export default Orders;

8.create mongoDb connection database

import mongoose from "mongoose";
const connect= async () => {
  try {
    await mongoose.connect(process.env.MONGO_DB);
  } catch (err) {
    throw new Error("connection failed !");
  }
};
export default connect;

conclusion 

Server-side form validation with Zod and Next.js Server Actions provides a robust and efficient solution for managing user input. By combining Zod's schema-driven validation with the server-side capabilities of Next.js, developers can build reliable, type-safe forms that ensure data consistency and deliver a smooth user experience. This approach not only enhances application security but also simplifies the development process, allowing for cleaner and more maintainable code.

Explore the latest insights, articles,free components, and expert advice on programming and software development

© Copyright 2024 MED DAKIR. All rights reserved./privacy policyTerms & Conditions