Server Action — nima, qanday ishlaydi, form bilan ishlatish
Muammodan boshlaylik
Siz forma yozdingiz — foydalanuvchi yangi mahsulot qo'shadi. Odatdagi yo'l:
app/api/products/route.ts— API endpoint yozish- Client Component da
fetch('/api/products', { method: 'POST', ... }) - Loading, error holatlarini boshqarish
- Muvaffaqiyatli bo'lsa sahifani yangilash
Bu 4 ta qadam. Har bir forma uchun shu takrorlanadi.
Server Action bilan — API endpoint kerak emas. Server funksiyasini to'g'ridan-to'g'ri formadan chaqirish mumkin.
Server Action nima
Server Action — serverda ishlaydigan funksiya. Lekin uni Client Component dan, hatto oddiy HTML formadan chaqirish mumkin.
Ichida qanday ishlaydi? Next.js Server Action uchun avtomatik API endpoint yaratadi. Forma yuborilganda — shu endpoint ga so'rov ketadi, funksiya serverda ishlaydi. Siz bu endpointni ko'rmaysiz, yozmaysiz.
Eng oddiy misol
// app/products/page.tsx — Server Component
export default function ProductsPage() {
async function addProduct(formData: FormData) {
'use server' // ← shu qator funksiyani Server Action qiladi
const name = formData.get('name') as string
const price = Number(formData.get('price'))
await db.insert(products).values({ name, price })
}
return (
<form action={addProduct}>
<input name="name" placeholder="Mahsulot nomi" />
<input name="price" placeholder="Narxi" type="number" />
<button type="submit">Qo'shish</button>
</form>
)
}
'use server' — funksiyani Server Action deb belgilaydi. form action={addProduct} — forma yuborilganda shu funksiya chaqiriladi.
JavaScript o'chirilgan bo'lsa ham ishlaydi — oddiy HTML forma kabi.
Alohida faylda Server Action
Ko'p joyda ishlatilsa — alohida faylga chiqarish:
// app/actions/products.ts
'use server' // ← fayl boshida — barcha funksiyalar Server Action
import { revalidatePath } from 'next/cache'
export async function addProduct(formData: FormData) {
const name = formData.get('name') as string
const price = Number(formData.get('price'))
await db.insert(products).values({ name, price })
revalidatePath('/products') // sahifani yangilash
}
export async function deleteProduct(id: number) {
await db.delete(products).where(eq(products.id, id))
revalidatePath('/products')
}
// app/products/page.tsx
import { addProduct } from '@/app/actions/products'
export default function ProductsPage() {
return (
<form action={addProduct}>
<input name="name" />
<button type="submit">Qo'shish</button>
</form>
)
}
Client Component da Server Action
Client Component da Server Action ni useTransition bilan ishlatish — loading holati va xatolarni boshqarish uchun:
// app/products/AddProductForm.tsx
'use client'
import { useTransition } from 'react'
import { addProduct } from '@/app/actions/products'
export function AddProductForm() {
const [isPending, startTransition] = useTransition()
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const formData = new FormData(e.currentTarget)
startTransition(async () => {
await addProduct(formData)
})
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Mahsulot nomi" />
<input name="price" placeholder="Narxi" type="number" />
<button type="submit" disabled={isPending}>
{isPending ? 'Qo\'shilmoqda...' : 'Qo\'shish'}
</button>
</form>
)
}
useTransition — Server Action ishlayotganda isPending = true. Tugaganda false. Tugma disabled bo'ladi — ikki marta bosilmaydi.
useActionState — forma holati
Next.js 15 / React 19 da useActionState — forma holati va xatolarni boshqarish uchun:
// app/actions/products.ts
'use server'
export async function addProduct(prevState: any, formData: FormData) {
const name = formData.get('name') as string
if (!name || name.length < 2) {
return { error: 'Nom kamida 2 ta harf bo\'lishi kerak' }
}
await db.insert(products).values({ name })
return { success: true }
}
// AddProductForm.tsx
'use client'
import { useActionState } from 'react'
import { addProduct } from '@/app/actions/products'
export function AddProductForm() {
const [state, action, isPending] = useActionState(addProduct, null)
return (
<form action={action}>
<input name="name" placeholder="Mahsulot nomi" />
{state?.error && (
<p style={{ color: 'red' }}>{state.error}</p>
)}
{state?.success && (
<p style={{ color: 'green' }}>Qo'shildi!</p>
)}
<button type="submit" disabled={isPending}>
{isPending ? 'Qo\'shilmoqda...' : 'Qo\'shish'}
</button>
</form>
)
}
useActionState — Server Action ning natijasini (state) va loading holatini (isPending) beradi.
bind — qo'shimcha argument uzatish
Forma ma'lumotidan tashqari qo'shimcha argument kerak bo'lsa — bind:
// app/products/page.tsx
import { deleteProduct } from '@/app/actions/products'
export default async function ProductsPage() {
const products = await db.select().from(productsTable)
return (
<ul>
{products.map(product => {
const deleteWithId = deleteProduct.bind(null, product.id)
return (
<li key={product.id}>
{product.name}
<form action={deleteWithId}>
<button type="submit">O'chirish</button>
</form>
</li>
)
})}
</ul>
)
}
// actions/products.ts
export async function deleteProduct(id: number, formData: FormData) {
'use server'
await db.delete(products).where(eq(products.id, id))
revalidatePath('/products')
}
bind(null, product.id) — id ni oldindan "bog'lash". Forma yuborilganda id birinchi argument sifatida keladi.
Tez-tez uchraydigan xatolar
1. Server Action da 'use server' yo'q
// ❌ — oddiy funksiya, Server Action emas
async function addProduct(formData: FormData) {
await db.insert(...) // xato! DB brauzerda yo'q
}
// ✅
async function addProduct(formData: FormData) {
'use server'
await db.insert(...)
}
2. Server Action ni Client Component da to'g'ridan-to'g'ri e'lon qilish
// ❌ — Client Component ichida 'use server' bo'lmaydi
'use client'
export function Form() {
async function submit(formData: FormData) {
'use server' // xato! Client Component ichida bo'lmaydi
}
}
// ✅ — alohida faylda e'lon qilib, import qilish
import { submit } from '@/app/actions'
3. revalidatePath ni unutish
// ❌ — DB yangilandi, lekin sahifa eski ma'lumot ko'rsatadi
export async function addProduct(formData: FormData) {
'use server'
await db.insert(products).values(...)
// revalidatePath yo'q — sahifa yangilanmaydi
}
// ✅
export async function addProduct(formData: FormData) {
'use server'
await db.insert(products).values(...)
revalidatePath('/products')
}
Xulosa
Server Action — serverda ishlaydigan funksiya, API endpoint kerak emas.
'use server' → funksiya yoki fayl darajasida
form action={fn} → forma yuborilganda chaqiriladi
useTransition → isPending holati
useActionState → forma holati + xato + isPending
bind(null, id) → qo'shimcha argument uzatish
revalidatePath → sahifani yangilash
JavaScript o'chirilgan bo'lsa ham ishlaydi — oddiy HTML forma kabi.
Keyingi dars
23-dars: Server Action — optimistic update, revalidatePath, revalidateTag
Server Action yuborilganda foydalanuvchi javob kutmasdan darhol UI yangilanadi — optimistic update.