فرم‌ها در وب‌سایت‌های مدرن بسیار ضروری هستند، زیرا به ما کمک می‌کنند تا اطلاعات کاربران را جمع‌آوری کنیم. بنابراین، آگاهی از نحوه مدیریت صحیح فرم‌ها هنگام ساخت برنامه‌های وب بسیار مهم می‌باشد. در این مقاله قصد داریم تا نحوه مدیریت فرم در Next.js با استفاده از Server Action و Zod را باهم بررسی کنیم.

مقدمه‌ای بر Server Action در Next.js

منظور از Server Action چیست؟ Server Actionها دقیقاً همان‌طور که از اسمشان مشخص است، اکشن‌هایی هستند که بر روی سرور اجرا می‌شوند. با استفاده از Server Actionها، می‌توانیم درخواست‌هایی به APIهای خارجی ارسال کنیم و یا این که داده‌هایی را از دیتابیس دریافت نماییم.

قبل از نسخه ۱۳ Next.js، برای مدیریت درخواست‌های API و ارسال فرم‌ها باید از routeها استفاده می‌کردیم، که این کار پیچیده و زمان‌بر بود. اما با معرفی Server Actionها، اکنون می‌توانیم مستقیماً در کامپوننت‌های Next.js خود با APIهای خارجی و دیتابیس‌ها ارتباط برقرار کنیم.

با اجرای Server Actionها در سرور، پردازش داده‌ها به‌صورت ایمن انجام می‌شود و خطرات امنیتی کاهش می‌یابد. Server Actionها همچنین در مدیریت فرم‌ها مفید هستند. زیرا، این امکان را به ما می‌دهند که به طور مستقیم با سرور خود ارتباط برقرار کنیم و از این طریق دسترسی به اطلاعات حساس را برای کلاینت محدود نماییم.

دو روش برای ساخت Server Action وجود دارد:

async function getPosts() {
  "use server"; // this makes getPosts a server actions

  // rest of code
}
// action.ts

"use server";

export async function getPosts() {
  const res = await fetch("https:...");
  const data = res.json();

  return data;
}

در مثال کد بالا، getPosts یک Server Action است.

مقدمه‌ای بر Zod برای اعتبارسنجی

Zod یک کتابخانه اعتبارسنجی است که می‌توانیم از آن برای اعتبارسنجی ورودی‌های فرم در سمت سرور استفاده کنیم. این کار اطمینان حاصل می‌کند که هم در سمت کلاینت و هم در سمت سرور یکپارچگی داده‌ها حفظ شود.

Zod یک کتابخانه مبتنی بر تایپ‌اسکریپت است، به این معنی که به‌طور پیش‌فرض با type safety همراه می‌باشد.

برای نصب کتابخانه Zod در برنامه Next.js خود، باید از دستور زیر استفاده کنیم:

npm install zod

در هسته کتابخانه Zod، اسکیماها قرار دارند. ما می‌توانیم از schemaها برای اعتبارسنجی ورودی‌ها استفاده کنیم. در ادامه نحوه تعریف یک schema را بررسی می‌کنیم:

import { z } from "zod";

const contactSchema = z.object({
  name: z.string().min(2, { message: "Name must be at least 2 characters" }),
  email: z.string().email({ message: "Invalid email address" }),
  message: z
    .string()
    .min(10, { message: "Message must be at least 10 characters" }),
});

در داخل contactSchema مشخص می‌کنیم که:

ویژگی message پیامی است که زمانی که همه یا هر یک از اعتبارسنجی‌ها شکست بخورد، بر روی صفحه نمایش داده می‌شود.

چگونه باید کامپوننت فرم تماس را بسازیم؟

در این بخش، ما رابط کاربری فرم تماس را می‌سازیم.

داخل دایرکتوری app، یک پوشه به نام components ایجاد می‌کنیم.سپس، داخل پوشه components یک فایل جدید به نام contactForm.tsx می‌سازیم و کد زیر را به آن اضافه می‌کنیم:

"use client";

function ContactForm() {
  return (
    <form action="">
      <input type="text" name="name" placeholder="Enter your name" />
      <input type="email" name="email" placeholder="Enter your email" />
      <textarea name="message" cols={30} rows={10} placeholder="Type in your message"></textarea>
      <button type="submit">Send Message</button>
    </form>
  );
}

export default ContactForm;

در کد بالا، ما یک فرم تماس ساده ساخته‌ایم و این فرم را به یک کلاینت کامپوننت تبدیل کرده‌ایم؛ در ادامه دلیل این کار را بررسی خواهیم کرد.

کامپوننت ContactForm را در فایل page.tsx خود import می‌کنیم:

import ContactForm from "./components/contactForm.tsx";

function Home() {
  return (
    <div>
      <h2>Contact Form</h2>
      <ContactForm />
    </div>
  );
}

چگونه باید Server Actionها را بسازیم و داده‌های فرم را با استفاده از zod اعتبارسنجی کنیم؟

در این بخش از مقاله قصد داریم تا Server Actionها را ایجاد کرده و داده‌های فرم را با استفاده از zod اعتبارسنجی کنیم.

در پوشه app، یک پوشه دیگر به نام api می‌سازیم. داخل پوشه api، یک فایل به نام action.ts ایجاد کرده و کد زیر را در آن قرار می‌دهیم:

"use server";

import { z } from "zod";

const contactFormSchema = z.object({
  name: z.string().trim().min(1, { message: "Name field is required" }),
  email: z.string().email({ message: "Invalid email address" }),
  message: z.string().trim().min(1, { message: "Please type in a message" }),
});

export async function sendEmail(prevState: any, formData: FormData) {
  const contactFormData = Object.fromEntries(formData);
  const validatedContactFormData = contactFormSchema.safeParse(contactFormData);


  if (!validatedContactFormData.success) {
    const formFieldErrors =
      validatedContactFormData.error.flatten().fieldErrors;

    return {
      errors: {
        name: formFieldErrors?.name,
        email: formFieldErrors?.email,
        message: formFieldErrors?.message,
      },
    };
  }

  return {
    success: "Your message was sent successfully!",
  };
}

در کد بالا، ما یک contactFormSchema برای اعتبارسنجی ورودی‌های فرم خود تعریف کرده‌ایم.

تابع sendEmail که Server Action ما است، دو آرگومان می‌پذیرد:

FormData این امکان را فراهم می‌کند که تابع ما بدون نیاز به استفاده از هوک useState به فیلدهای فرم دسترسی پیدا کند، و این کار بر اساس ویژگی name انجام می‌شود.

ما از Object.fromEntries() برای تبدیل داده‌های خام formData به یک آبجکت جاوااسکریپت معمولی استفاده می‌کنیم و آن را در متغیر contactFormData ذخیره می‌نماییم.

سپس، داده‌های contactFormData را با استفاده از متد safeParse() از اسکیما Zod، یعنی contactFormSchema اعتبارسنجی می‌کنیم.

به عنوان یک تمرین خوب در برنامه‌نویسی، به صورت زود هنگام بررسی می‌کنیم که آیا اعتبارسنجی شکست خورده است یا خیر. اگر اعتبارسنجی شکست بخورد، یک آبجکت با ویژگی error return می‌کنیم که شامل پیغام خطای هر فیلد فرم می‌باشد.

مقدار formFieldsError به آبجکت error از Zod نسبت داده می‌شود، که پیغام خطای هر فیلد فرم را در بر می‌گیرد.

اگر همه چیز خوب پیش برود، تنها یک آبجکت error با ویژگی success return می‌کنیم.

چگونه باید Server Action را به فرم تماس خود اضافه کنیم؟

در این بخش، قصد داریم برای مدیریت فرم تماس، Server Action را به آن اضافه نماییم. به فایل contactForm.tsx رفته و محتویات آن را با کدی که در این لینک قرار دارد، جایگزین می‌کنیم.

در این کدی که اکنون داریم، دو هوک useFormState و useFormStatus را از "react-dom" و sendEmail را از "api/action.ts". import کرده‌ایم.

سپس، یک متغیر initialState ایجاد کرده‌ایم تا state اولیه را نگه دارد. این متغیر در هوک useFormState استفاده خواهد شد.

initialState یک آبجکت است که شامل موارد زیر می‌باشد:

درون کامپوننت ContactForm، از هوک useFormState استفاده می‌کنیم. این هوک دو آرگومان می‌گیرد: Server Action و state اولیه؛ و یک آرایه با دو مقدار return می‌کند: state فعلی و formAction.

formAction به prop action فرم پاس داده می‌شود. این امر مسئول ارسال فرم ما است که اعتبارسنجی Zod را شامل می‌شود.

زیر هر فیلد فرم، به‌طور شرطی پیام خطای مربوط به هر فیلد نمایش داده می‌شود.

اگر فرم با موفقیت ارسال شده باشد، در قسمت انتهای فرم نیز پیام موفقیت نمایش داده می‌شود.

دکمه ارسال به یک کامپوننت جداگانه به نام SubmitButton منتقل شده است تا بتوانیم از هوک useFormStatus استفاده کنیم.

هوک useFormStatus یک آبجکت return می‌کند که ویژگی pending را شامل می‌شود. زمانی که فرم در حال ارسال است، ما می‌توانیم از آن برای غیرفعال کردن دکمه ارسال استفاده کنیم.

از طریق این لینک می‌توانیم به فایل کامل پروژه دسترسی داشته باشیم.

جمع‌بندی

در این مقاله، با مفهوم Server Action آشنا شدیم و یاد گرفتیم چگونه باید از کتابخانه Zod استفاده کنیم. همچنین از Server Action و Zod برای ساخت و مدیریت یک فرم تماس استفاده کردیم. البته، Server Actionها فقط محدود به ارسال فرم نیستند و می‌توانند برای دریافت داده‌ها از API‌های خارجی و دیتابیس‌ها نیز مورد استفاده قرار بگیرند.