Server Action — nima, qanday ishlaydi, form bilan ishlatish

~25 daq · Next.js · Ma'lumot Olish

Muammodan boshlaylik

Siz forma yozdingiz — foydalanuvchi yangi mahsulot qo'shadi. Odatdagi yo'l:

  1. app/api/products/route.ts — API endpoint yozish
  2. Client Component da fetch('/api/products', { method: 'POST', ... })
  3. Loading, error holatlarini boshqarish
  4. 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.