مدیریت فرم با استفاده از Server Action و Zod در Next.js

فرم‌ها در وب‌سایت‌های مدرن بسیار ضروری هستند، زیرا به ما کمک می‌کنند تا اطلاعات کاربران را جمع‌آوری کنیم. بنابراین، آگاهی از نحوه مدیریت صحیح فرم‌ها هنگام ساخت برنامه‌های وب بسیار مهم می‌باشد. در این مقاله قصد داریم تا نحوه مدیریت فرم در 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 وجود دارد:

  • روش اول استفاده از دستور
    "use server"
    "use server" در سطح بالای یک تابع است. ما می‌توانیم از این روش فقط در داخل یک سرور کامپوننت استفاده کنیم. استفاده از آن در یک کلاینت کامپوننت باعث بروز خطا خواهد می‌شود. به عنوان مثال:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
async function getPosts() {
"use server"; // this makes getPosts a server actions
// rest of code
}
async function getPosts() { "use server"; // this makes getPosts a server actions // rest of code }
async function getPosts() {
  "use server"; // this makes getPosts a server actions

  // rest of code
}
  • روش دوم برای ساخت Server Action در Next.js این است که یک فایل جداگانه بسازیم و دستور
    "use server"
    "use server" را در بالای آن فایل قرار دهیم. این کار اطمینان می‌دهد که هر تابع async که از این فایل export می‌شود، یک Server Action به حساب می‌آید. به عنوان مثال:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// action.ts
"use server";
export async function getPosts() {
const res = await fetch("https:...");
const data = res.json();
return data;
}
// action.ts "use server"; export async function getPosts() { const res = await fetch("https:..."); const data = res.json(); return data; }
// action.ts

"use server";

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

  return data;
}

در مثال کد بالا،

getPosts
getPosts یک Server Action است.

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

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

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

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install zod
npm install zod
npm install zod

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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" }),
});
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" }), });
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
contactSchema مشخص می‌کنیم که:

  • name
    name از نوع
    string
    string است و باید حداقل ۲ کاراکتر داشته باشد،
  • email
    email از نوع
    string
    string و
    email
    email است و باید یک ایمیل معتبر باشد،
  • message
    message از نوع
    string
    string است و باید حداقل ۱۰ کاراکتر داشته باشد.

ویژگی

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

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

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

داخل دایرکتوری

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"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;
"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;
"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
ContactForm را در فایل
page.tsx
page.tsx خود import می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import ContactForm from "./components/contactForm.tsx";
function Home() {
return (
<div>
<h2>Contact Form</h2>
<ContactForm />
</div>
);
}
import ContactForm from "./components/contactForm.tsx"; function Home() { return ( <div> <h2>Contact Form</h2> <ContactForm /> </div> ); }
import ContactForm from "./components/contactForm.tsx";

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

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

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

در پوشه

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"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!",
};
}
"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!", }; }
"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
contactFormSchema برای اعتبارسنجی ورودی‌های فرم خود تعریف کرده‌ایم.

تابع

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

  • prevState
    prevState که برای نمایش پیام‌های خطا و موفقیت استفاده می‌شود، و
  • formData
    formData که ورودی‌های فرم ما هستند.

FormData این امکان را فراهم می‌کند که تابع ما بدون نیاز به استفاده از هوک

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

ما از

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

سپس، داده‌های

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

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

error
error return می‌کنیم که شامل پیغام خطای هر فیلد فرم می‌باشد.

مقدار

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

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

success
success return می‌کنیم.

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

در این بخش، قصد داریم برای مدیریت فرم تماس، Server Action را به آن اضافه نماییم. به فایل

contactForm.tsx
contactForm.tsx رفته و محتویات آن را با کدی که در این لینک قرار دارد، جایگزین می‌کنیم.

در این کدی که اکنون داریم، دو هوک

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

سپس، یک متغیر

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

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

  • یک ویژگی
    success
    success برای پیام موفقیت Server Action،
  • و یک آبجکت
    errors
    errors که برابر با آبجکت
    errors
    errors است، که در صورت شکست اعتبارسنجی در return ،Server Action می‌کنیم.

درون کامپوننت

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

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

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

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

دکمه ارسال به یک کامپوننت جداگانه به نام

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

هوک

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

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

جمع‌بندی

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

دیدگاه‌ها:

افزودن دیدگاه جدید