جاري التحميل...
جاري التحميل...
Server Functions، النماذج، useActionState، revalidation
Server Actions هي دوال تنفذ على الخادم وتُستدعى من مكونات العميل. هي الطريقة الأساسية لتعديل البيانات في Next.js.
بدلاً من إنشاء مسارات API منفصلة لكل تعديل، تكتب الدالة مباشرة في ملف المكون.
أضف 'use server' في بداية الدالة لتحويلها إلى Server Action. يجب أن تكون غير متزامنة (async).
مرر Server Action إلى خاصية action في النموذج. Next.js يتعامل مع الاتصال بالخادم تلقائياً.
useActionState من React 19 يدير حالة النموذج مع Server Actions. يعيد الحالة الحالية، ودالة الإجراء، وحالة isPending.
بعد تعديل البيانات، استخدم revalidatePath أو revalidateTag لتحديث البيانات المخزنة مؤقتاً.
ما هي الطريقة الصحيحة لتعيين دالة كـ Server Action؟
ماذا يحدث عند استدعاء revalidatePath('/') بعد تعديل البيانات؟
ما هو الدور الرئيسي لـ useActionState في React 19؟
ابنِ تطبيق Todo كامل مع Server Actions للإضافة والحذف والتبديل.
الحل:
// الحل الكامل
// 1. ملف app/actions.js
"use server";
import { revalidatePath } from "next/cache";
export async function addTodo(prevState, formData) {
const title = formData.get("title")?.trim();
if (!title || title.length < 2) {
return { success: false, error: "المهمة يجب أن تكون حرفين على الأقل" };
}
await db.todos.create({
data: { title, completed: false },
});
revalidatePath("/todos");
return { success: true, error: null };
}
export async function deleteTodo(id) {
await db.todos.delete({ where: { id } });
revalidatePath("/todos");
}
export async function toggleTodo(id, completed) {
await db.todos.update({
where: { id },
data: { completed: !completed },
});
revalidatePath("/todos");
}
// 2. ملف app/todos/page.js
import { Suspense } from "react";
import TodoForm from "@/components/TodoForm";
import TodoList from "@/components/TodoList";
export default function TodosPage() {
return (
<div className="p-8 max-w-lg mx-auto">
<h1 className="text-3xl font-bold mb-8">إدارة المهام</h1>
<TodoForm />
<Suspense fallback={<p>جاري تحميل المهام...</p>}>
<TodoList />
</Suspense>
</div>
);
}
// 3. ملف components/TodoForm.js
"use client";
import { useActionState } from "react";
import { addTodo } from "@/app/actions";
export default function TodoForm() {
const [state, formAction, isPending] = useActionState(addTodo, {
success: false,
error: null,
});
return (
<form action={formAction} className="flex gap-2 mb-6">
<input
type="text"
name="title"
placeholder="مهمة جديدة..."
className="flex-1 p-2 border rounded"
disabled={isPending}
/>
<button
type="submit"
disabled={isPending}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
{isPending ? "..." : "إضافة"}
</button>
{state.error && (
<p className="text-red-500 text-sm w-full">{state.error}</p>
)}
</form>
);
}
// 4. ملف components/TodoList.js
import { db } from "@/lib/database";
import TodoItem from "./TodoItem";
export default async function TodoList() {
const todos = await db.todos.findMany({
orderBy: { createdAt: "desc" },
});
if (todos.length === 0) {
return <p className="text-gray-500 text-center">لا توجد مهام بعد</p>;
}
return (
<div className="space-y-2">
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</div>
);
}
// 5. ملف components/TodoItem.js
"use client";
import { useTransition } from "react";
import { deleteTodo, toggleTodo } from "@/app/actions";
export default function TodoItem({ todo }) {
const [isPending, startTransition] = useTransition();
return (
<div className="flex items-center gap-3 p-3 border rounded-lg">
<input
type="checkbox"
checked={todo.completed}
onChange={() => {
startTransition(() => toggleTodo(todo.id, todo.completed));
}}
disabled={isPending}
/>
<span className={todo.completed ? "line-through text-gray-400" : ""}>
{todo.title}
</span>
<button
onClick={() => {
startTransition(() => deleteTodo(todo.id));
}}
disabled={isPending}
className="mr-auto text-red-500 text-sm"
>
حذف
</button>
</div>
);
}أساسيات Server Actions:
// تعريف Server Action
"use server";
export async function addItem(formData) {
const name = formData.get("name");
await db.items.create({ data: { name } });
revalidatePath("/items");
}
// استخدام مع النموذج
<form action={addItem}>
<input name="name" />
<button type="submit">إرسال</button>
</form>
// استخدام مع onClick
"use client";
import { useTransition } from "react";
function Button({ action }) {
const [isPending, startTransition] = useTransition();
return (
<button onClick={() => startTransition(action)}
disabled={isPending}>
{isPending ? "..." : "نقر"}
</button>
);
}useActionState:
"use client";
import { useActionState } from "react";
const initialState = { error: null };
async function action(prev, formData) {
const name = formData.get("name");
if (!name) return { error: "الاسم مطلوب" };
return { error: null };
}
function Form() {
const [state, formAction, isPending] =
useActionState(action, initialState);
return (
<form action={formAction}>
<input name="name" disabled={isPending} />
{state.error && <p>{state.error}</p>}
<button disabled={isPending}>إرسال</button>
</form>
);
}التحقق من الصلابة:
import { revalidatePath, revalidateTag }
from "next/cache";
revalidatePath("/");
revalidatePath("/blog");
revalidateTag("products");إعادة التوجيه:
import { redirect } from "next/navigation";
export async function createItem(formData) {
// ... حفظ البيانات
redirect("/items");
}كيف تنشئ Server Action؟
الإجابة:
أضف 'use server' في أعلى الملف.
ما الفائدة؟
الإجابة:
تalam النماذج بدون API routes منفصلة.
استخدم useActionState
يدير حالة النموذج تلقائياً مع Server Actions في React 19