Formulare în React
Controlled vs uncontrolled, FormData, useActionState, useFormStatus, erori accesibile.
Actualizat
Ce înseamnă un formular în React
Un formular are trei lucruri de gestionat: valorile câmpurilor, erorile de validare și starea trimiterii (pending, succes, eroare de server). React îți dă două moduri fundamentale de a ține valorile, iar toate librăriile de formulare sunt construite peste unul dintre ele.
Baza HTML (name, label, tipuri de input, validare nativă) e în lecția Formulare HTML — aici construim peste ea.
Controlled vs uncontrolled
Diferența dintre un input controlled și unul uncontrolled:
| Controlled | Uncontrolled | |
|---|---|---|
| Unde stă valoarea | în state React (useState) |
în DOM (input-ul o ține singur) |
| Cum o citești | din variabilă, oricând | la submit: FormData sau ref |
| Re-randare la fiecare tastă | da | nu |
| Cod | value + onChange pe fiecare câmp |
doar name (+ defaultValue) |
| Când | valoarea influențează UI-ul în timp real: căutare live, câmpuri dependente, formatare (telefon, card) | majoritatea formularelor: login, contact, setări |
// controlled
const [email, setEmail] = useState('')
<input value={email} onChange={e => setEmail(e.target.value)} />
// uncontrolled
<input name="email" defaultValue={user.email} />value fără onChange = input blocat (read-only). defaultValue = valoare inițială, apoi DOM-ul preia controlul.
Citirea datelor: FormData
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const form = new FormData(e.currentTarget)
const email = String(form.get('email'))
const tags = form.getAll('tags') // checkbox-uri cu același name → array
const data = Object.fromEntries(form) // ⚠️ păstrează doar ultima valoare pentru name-uri repetate
}Valorile din FormData sunt mereu stringuri (sau File). Un checkbox bifat trimite 'on', unul nebifat nu apare deloc.
React 19: formulare cu acțiuni
React 19 permite să dai o funcție direct în action. React se ocupă de preventDefault, de FormData și de starea pending.
'use client'
import { useActionState } from 'react'
import { useFormStatus } from 'react-dom'
type State = { error?: string; ok?: boolean }
async function subscribe(prev: State, formData: FormData): Promise<State> {
const email = String(formData.get('email') ?? '')
if (!email.includes('@')) return { error: 'Email invalid' }
await api('/subscribe', { method: 'POST', body: formData })
return { ok: true }
}
function SubmitButton() {
const { pending } = useFormStatus() // citește starea formularului părinte
return <button disabled={pending}>{pending ? 'Se trimite…' : 'Abonează-mă'}</button>
}
export function Newsletter() {
const [state, action] = useActionState(subscribe, {})
return (
<form action={action}>
<input name="email" type="email" required />
{state.error && <p role="alert">{state.error}</p>}
<SubmitButton />
</form>
)
}| Hook | Ce îți dă | Unde |
|---|---|---|
useActionState(fn, initial) |
[state, action, isPending] — rezultatul ultimei trimiteri |
componenta cu <form> |
useFormStatus() |
{ pending, data } al formularului părinte |
o componentă copil a formularului |
useOptimistic |
UI actualizat înainte de răspuns | liste, like-uri |
Cu o Server Action în loc de funcția client, același formular merge și fără JavaScript.
Erori accesibile
O eroare trebuie să fie văzută și auzită:
<label htmlFor="email">Email</label>
<input id="email" name="email" aria-invalid={!!error} aria-describedby={error ? 'email-error' : undefined} />
{error && <p id="email-error" role="alert">{error}</p>}| Atribut | Rol |
|---|---|
aria-invalid |
screen reader-ul anunță „invalid”; stilizezi cu aria-invalid:border-danger |
aria-describedby |
leagă mesajul de câmp — e citit când userul ajunge pe câmp |
role="alert" / aria-live |
mesajul e anunțat imediat ce apare |
Când îți trebuie o librărie
| Formularul are... | Fără librărie | Cu librărie (React Hook Form / Conform) |
|---|---|---|
| 2–5 câmpuri, validare simplă | ✓ useActionState + FormData |
inutil |
| validare la blur / la tastare, mesaje per câmp | greoi | ✓ |
| liste dinamice (adaugă / șterge rânduri) | greoi | ✓ |
| wizard în mai mulți pași, câmpuri dependente | greoi | ✓ |
Pe scurt
- Uncontrolled +
FormDatapentru majoritatea formularelor; controlled când valoarea schimbă UI-ul live. - React 19:
action={fn},useActionStatepentru rezultat,useFormStatuspentru pending. - Erori:
aria-invalid+aria-describedby+role="alert".