webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Unelte
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Formulare
  12. 12Date și backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animații
  16. 16Testare
  17. 17Arhitectură
Formulare · Lecția 4 din 4

Formulare cu Server Actions

Fluxul complet, valori păstrate după eroare, și RHF vs Conform vs TanStack Form.

Actualizat

Ce înseamnă un formular cu Server Action

În Next.js, cel mai direct mod de a trimite un formular e o Server Action: formularul trimite FormData direct la o funcție de pe server, fără API route și fără fetch scris de mână. Lecția asta pune cap la cap tot ce ai învățat: formular → validare Zod pe server → erori înapoi în UI → invalidare de cache.

Fluxul complet

Dacă datele sunt invalide, acțiunea returnează erorile, iar formularul le afișează:

formFormDataaction→Server ActionsafeParse cu Zodinvalid→UIerori per câmp + valorile păstrate

Dacă sunt valide, acțiunea salvează și reîmprospătează datele:

Server Actiondate validesalvează→DBupdateTag / redirect→UIdate proaspete

Implementarea

// features/signup/api/signup-action.ts
'use server'

import { z } from 'zod'
import { redirect } from 'next/navigation'
import { signupSchema } from '../model/signup-schema'

export type SignupState = {
  errors?: Partial<Record<keyof z.infer<typeof signupSchema>, string[]>>
  values?: Record<string, string>
  message?: string
}

export async function signup(prev: SignupState, formData: FormData): Promise<SignupState> {
  const raw = Object.fromEntries(formData) as Record<string, string>
  const result = signupSchema.safeParse(raw)
  if (!result.success) return { errors: z.flattenError(result.error).fieldErrors, values: raw }

  const exists = await db.user.findUnique({ where: { email: result.data.email } })
  if (exists) return { errors: { email: ['Există deja un cont cu acest email'] }, values: raw }

  await db.user.create({ data: result.data })
  redirect('/welcome')
}
// features/signup/ui/signup-form.tsx
'use client'
import { useActionState } from 'react'
import { signup } from '../api/signup-action'

export function SignupForm() {
  const [state, action, pending] = useActionState(signup, {})
  return (
    <form action={action}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" defaultValue={state.values?.email} aria-invalid={!!state.errors?.email} aria-describedby="email-error" />
      <p id="email-error" role="alert">{state.errors?.email?.[0]}</p>
      <button disabled={pending}>Creează cont</button>
    </form>
  )
}

Detalii care contează:

Detaliu De ce
returnezi values și le pui în defaultValue după o eroare, React resetează formularul — fără asta userul pierde ce a scris
erori de business (email existent) în același format UI-ul le afișează la fel ca pe cele de validare
redirect / updateTag doar la succes read-your-own-writes — vezi Cache
auth + autorizare la începutul acțiunii acțiunea e un endpoint public

Validare și pe client — opțiunile

Fluxul de mai sus validează doar pe server: corect și sigur, dar userul vede erorile abia după trimitere. Pentru feedback instant:

Abordare Cum Plusuri Minusuri
doar useActionState ca mai sus, + atribute HTML (required, type) simplu, merge fără JS erori abia la submit
React Hook Form + Server Action RHF validează cu zodResolver, apoi apelezi acțiunea cu datele UX excelent, ecosistem mare două modele mentale, nu merge fără JS
Conform construit special pentru Server Actions: aceeași schemă, validare pe client și server, merge fără JS progressive enhancement real comunitate mai mică
TanStack Form librărie nouă, complet tipată, agnostică de framework tipuri foarte bune, performant mai verbos, mai nou
Formik librăria „clasică” mult cod vechi o folosește întreținută slab — nu pentru proiecte noi

Cum alegi

  1. Formular simplu (login, contact, newsletter) → useActionState + Zod pe server.
  2. Formular complex (multe câmpuri, liste dinamice, validare live) → React Hook Form + Zod, cu aceeași schemă în Server Action.
  3. Vrei progressive enhancement complet și validare pe ambele părți → Conform.

Indiferent de alegere: schema Zod e una singură, iar serverul validează mereu.

Pe scurt

  • Form → Server Action → safeParse → erori + valori înapoi, sau salvare + updateTag / redirect.
  • Păstrează valorile după eroare; erorile de business în același format.
  • Simplu → useActionState; complex → React Hook Form; progressive enhancement → Conform.

Surse oficiale

Exerciții

Ți-a fost utilă pagina?

Un click — fără cont.