Con React 19 le Actions portano nel core un modo nativo per gestire mutazioni e invii dei form con meno boilerplate, più accessibilità e una migliore integrazione con Server Actions dei framework. In questo articolo vediamo cos’è un’Action, come usare useActionState
e useFormStatus
, come gestire errori, pending e aggiornamenti ottimistici, e dove rimangono i limiti.
Cosa sono le Actions
Un’Action è una funzione (spesso asincrona) che esegue una mutazione in risposta a un’interazione utente, tipicamente l’invio di un form. In React 19 puoi collegare direttamente un’Action all’attributo action
di un elemento <form>
o alla prop formAction
di un singolo bottone, senza dover gestire manualmente onSubmit
, stati di caricamento e propagazione degli errori.
Perché cambiano le cose rispetto a prima
- Meno stato “fatto a mano”: niente più catene di
useState
per pending/error. - Accessibilità migliore: il submit del form segue il comportamento HTML standard (tastiera, screen reader, ecc.).
- Compatibilità con Server Actions: nei framework come Next.js, le Actions si agganciano ai “server function endpoints” in modo naturale.
useActionState
useActionState
è un hook che incapsula la tua funzione di azione e ti restituisce: stato (qualunque valore tu scelga di ritornare, spesso errori), la funzione d’invio da passare a <form action={...}>
, e un booleano pending.
import { useActionState } from "react";
function UpdateName() {
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
const name = formData.get("name");
const res = await fetch("/api/user/name", {
method: "POST",
body: JSON.stringify({ name }),
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
return { message: "Aggiornamento non riuscito" }; // <-- diventa il nuovo state
}
return null; // nessun errore
},
null // initial state
);
return (
<form action={submitAction}>
<label>Nome</label>
<input name="name" />
{error && <p role="alert">{error.message}</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Salvataggio..." : "Salva"}
</button>
</form>
);
}
Nota: useActionState
accetta come primo argomento la tua funzione d’azione e come secondo lo stato iniziale; lo state che ritorni diventerà disponibile al render successivo.
Stato di invio dal basso: useFormStatus
useFormStatus
è un hook di react-dom
che fornisce informazioni sull’ultimo invio del form (ad esempio pending
) ed è pensato per essere usato in componenti figli del form, come un pulsante di submit riutilizzabile.
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Invio..." : "Invia"}
</button>
);
}
function Form({ action }) {
return (
<form action={action}>
<input name="email" type="email" required />
<SubmitButton />
</form>
);
}
Ricorda che useFormStatus
funziona solo all’interno dell’albero del form di riferimento. Se devi coordinare più form o pulsanti fuori dal form, valuta un wrapper che propaghi lo stato o usa direttamente isPending
da useActionState
.
Più pulsanti, più Actions: formAction sui bottoni
Puoi assegnare Actions diverse a bottoni differenti nello stesso form usando la prop formAction
. È utile per casi come “Salva bozza” vs “Invia per revisione”.
function Editor() {
const [state, submit, pending] = useActionState(savePost, null);
const [_, submitDraft] = useActionState(saveDraft, null);
return (
<form action={submit}>
<input name="title" />
<textarea name="body" />
<button type="submit" formAction={submitDraft}>Salva bozza</button>
<button type="submit" disabled={pending}>Pubblica</button>
</form>
);
}
Pattern di gestione errori e successi
- Errori di validazione: ritorna un oggetto con i messaggi; mostralo vicino ai campi e imposta
aria-invalid
/aria-describedby
. - Successo: ritorna
null
o un payload e reindirizza (se sei in un framework) oppure aggiorna lo stato UI.
const [formErrors, submit, pending] = useActionState(
async (prev, data) => {
const email = String(data.get("email") || "");
if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
return { email: "Email non valida" };
}
// ... mutazione ...
return null; // nessun errore
},
null
);
Ottimistico, transizioni e UX
React 19 incoraggia un flusso in cui pending, errori e redirect sono “di serie”. Per aggiornamenti ottimistici (es. aggiungi un item a lista) puoi combinare le Actions con useOptimistic
o con una cache esterna; per pending di UI più ampi puoi ancora usare useTransition
.
Server Actions e framework
Nei framework come Next.js (App Router), le Actions del form possono invocare direttamente Server Actions con action={myServerFn}
; la funzione riceve un FormData
e gira sul server, evitando fetch manuali e gestendo il ritorno di stato/errori.
// app/actions.ts
"use server";
export async function createInvoice(formData: FormData) {
// Validazione lato server
// Persistenza
return { ok: true };
}
// app/invoices/page.tsx
import { useActionState } from "react";
import { createInvoice } from "../actions";
export default function Page() {
const [state, submit, pending] = useActionState(createInvoice, null);
return (
<form action={submit}>
<input name="customerId" required />
<input name="amount" type="number" required />
<button type="submit" disabled={pending}>
{pending ? "Creazione..." : "Crea fattura"}
</button>
{state?.error && <p role="alert">{state.error}</p>}
</form>
);
}
FAQ pratiche
Posso passare qualsiasi funzione a <form action={...}>
?
No. Deve essere una funzione d’azione valida (ad es. quella restituita da useActionState
o una Server Action). Altrimenti React tratta action
come un normale attributo HTML stringa.
Come gestisco più form o pulsanti riutilizzabili?
Usa useFormStatus
all’interno dell’albero del form per leggere pending
nei componenti figlio; per pulsanti esterni usa l’isPending
dell’Action o soluzioni di stato condiviso.
Quando preferire librerie di form?
Le Actions coprono invio, pending ed errori di base. Per esigenze avanzate (validazione client-side schema-based, field arrays complessi, dirty tracking) potresti volerle combinare con librerie come React Hook Form; la community sta ancora esplorando l’integrazione.
Checklist di adozione
- Migra un form alla volta collegando la tua mutazione a
useActionState
. - Estrai il bottone di submit e usa
useFormStatus
per il pending. - Ritorna errori strutturati dall’Action e rendili accessibili (ARIA).
- Valuta
formAction
per bottoni con esiti diversi. - Se usi un framework, valuta Server Actions per evitare roundtrip manuali.
Conclusioni
Le Actions in React 19 spostano la gestione delle mutazioni più vicino all’HTML e al paradigma del form, riducendo codice e migliorando UX. Con useActionState
e useFormStatus
puoi costruire esperienze di invio robuste, accessibili e progressive, pronte ad agganciarsi a Server Actions quando servono.