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

#typescript #zod #server-side #form

Introduction to Zod and Its Role in Next.js: Modern web applications demand precise data validation to ensure a seamless user experience and robust backend processes. This is where Zod, a powerful TypeScript-first schema declaration and validation library, comes into play. In this article, we’ll explore how to integrate Zod into a Next.js project, ensuring efficient validation for both client and server.

Overview of Zod and Its Benefits Zod is a lightweight and intuitive library that allows developers to define schemas for data validation. Unlike traditional libraries like Yup or Joi, Zod is fully compatible with TypeScript, enabling strong type safety alongside runtime validation.

Here are some benefits of using Zod:

Seamless TypeScript integration. Concise and readable syntax. Support for nested schemas and advanced validations. Auto-generated TypeScript types from schemas. Comparison with Other Validation Libraries While other libraries like Yup or Joi are popular, they often require additional effort to integrate with TypeScript. Zod, on the other hand, is designed with TypeScript in mind, making it a go-to choice for projects that prioritize type safety.

Installing Zod Steps to Integrate Zod To add Zod to your project, run the following command:

npm i zod

Testing the Installation To confirm that Zod is installed, you can create a simple user schema

export const createAccountSchema=z.object({
  name:z
  name:z
    .string({ message:"name is required" })
    .min(6, "name must be more then 6 characters")
    .max(50, "name must be leas then 50 characters"),
  email:z
    .string({ message:"Email is required" })
    .min(16, "Email is required ")
    .email("Invalid email"),
  password:z
    .string({ message:"Password is required" })
    .min(1, "Password is required")
    .min(8, "Password must be more than 8 characters")
    .max(32, "Password must be less than 32 characters")
    .transform(hashPassword),
});

Install this package for more settings :

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

Create User sign-up component using typescript

"use client";
import React, { useActionState } from "react";
import Image from "next/image";
import { FcGoogle } from "react-icons/fc";
import Link from "next/link";
import {
  addUser,
} from "@/src/utils/actions";
import { CreateButton } from "../components/SearchButton";
import { parseWithZod } from "@conform-to/zod";
import { createAccountSchema } from "@/src/utils/ZodSchema";
import { useForm } from "@conform-to/react";

const Page = () => {
  const [lastResult, action] = useActionState(addUser, undefined);
  const [form, fields] = useForm({
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, {
        schema: createAccountSchema,
      });
    },
    shouldValidate: "onBlur",
    shouldRevalidate: "onInput",
  });
  return (
    <>
      <div className="flex w-full bg-white items-center justify-center dark:bg-dark py-32 p-16 lg:flex lg:flex-col-reverse md:p-10">
        <div className="m-2 w-full rounded-2xl bg-gray-400 bg-cover bg-center text-white md:hidden">
          <Image
            height={500}
            width={500}
            priority
            className="w-full h-full object-fill rounded-2xl"
            alt="image_login"
            src={
              "https://i.ibb.co/9VFgfg0/organic-flat-blog-post-illustration.jpg"
            }
          />
        </div>
        <div className="w-full sm:w-full md:mt-16">
            {/* form action register */}
            <form
              id={form.id}
              onSubmit={form.onSubmit}
              action={action}
              noValidate
              className="mt-4"
            >
              <div className="flex flex-col justify-center items-center">
                <input
                  className="w-full dark:bg-dark px-8 py-3 rounded-lg font-medium bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white"
                  type="text"
                  placeholder="full name"
                  name={fields.name.name}
                  defaultValue={fields.name.initialValue as string}
                  key={fields.name.key}
                  maxLength={40}
                  required
                />
                <p className="text-red-600 text-sm">
                  {fields.name.errors}
                </p>
                <input
                  className="w-full px-8 dark:bg-dark py-3 mt-2 rounded-lg font-medium bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white"
                  type="email"
                  placeholder="Email"
                  name={fields.email.name}
                  defaultValue={fields.email.initialValue as string}
                  key={fields.email.key}
                  required
                />
                <p className="text-red-600 text-sm">{fields.email.errors}</p>
                <input
                  className="w-full px-8 dark:bg-dark ml-2 sm:ml-0 py-3 mt-2 rounded-lg font-medium bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white"
                  type="password"
                  placeholder="Password"
                  name={fields.password.name}
                  key={fields.password.key}
                />
                <p className="text-red-600 text-sm">{fields.password.errors}</p>
              </div>
              <CreateButton />
            </form>
            <div className="bg-gray-400 h-[1px] w-full mt-5 mb-5"></div>
            <h4 className="flex justify-center underline text-gray-500 dark:text-light">
              Login with Google or Github
            </h4>
            <div className="flex-col">
            
            </div>
            <div className="mt-4 text-center">
              <Link
                href="/login"
                className="font-bold text-blue-600 no-underline hover:text-blue-400"
              >
                <span className="text-sm text-gray-600 dark:text-light">
                  Do you have an account ?
                  <span className="text-blue-500 hover:underline"> Login</span>
                </span>
              </Link>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Page;

Create CreateButton.tsx

export function CreateButton() {
  const status = useFormStatus();
  const status = useFormStatus();
  return (
    <button
      type="submit"
      className="mt-5 tracking-wide  font-semibold bg-[#2b7aa6] text-white-500 w-full py-2 rounded-lg hover:bg-[#2b9aa2] transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none"
    >
      <span className="text-light">
        {status.pending ? "Creating..." : "Create Account"}
      </span>
    </button>
  );
}

Create addUser as a server function

import { redirect } from"next/navigation";
import { revalidatePath } from"next/cache";
import { connect } from"./ConnectDB";
import { parseWithZod } from"@conform-to/zod";
import { createAccountSchema } from"./ZodSchema";


export const addUser=async (prevState:unknown, formData:FormData) => {
  const submission=parseWithZod(formData, {
    schema:createAccountSchema,
  });
  if (submission.status!=="success") {
    return submission.reply();
  }
  try {
    connect();
    const newUser=newUser({
      name:submission.value.name,
      email:submission.value.email,
      password:submission.value.password,
    });
    await newUser.save();
    console.log("user is save");
  } catch (error) {
    console.log(error);
  }
  revalidatePath("/create-account");
  redirect("/login");
};

Create connect mongoDb Function:

install mongoDb and mongoose

npm install mongoose && npm install mongoose --save

create MongoDB connect.tsx

// Importing mongoose library along with Connection type from it
importmongoose, { Connection } from"mongoose";

// Declaring a variable to store the cached database connection
let cachedConnection:Connection|null=null;

// Function to establish a connection to MongoDB
export asyncfunction connect() {
  // If a cached connection exists, return it
  if (cachedConnection) {
    console.log("Using cached db connection");
    return cachedConnection;
  }
  try {
    // If no cached connection exists, establish a new connection to MongoDB
    if (!process.env.MONGO) {
      throw new Error("MONGO environment variable is not defined");
    }
    const cnx=await mongoose.connect(process.env.MONGO);
    // Cache the connection for future use
    cached Connection=cnx.connection;
    // Log message indicating a new MongoDB connection is established
    console.log("New mongodb connection established");
    // Return the newly established connection
    return cached Connection;
  } catch (error) {
    // If an error occurs during connection, log the error and throw it
    console.log(error);
    throw error;
  }
}

Conclusion Zod, combined with TypeScript and Next.js, is a game-changer for modern web development. It simplifies validation, improves type safety, and ensures your applications are robust and maintainable. Whether you're validating API requests or form inputs, Zod makes it a breeze to keep your codebase clean and error-free.

Comments

Share this content

Copy this URL

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