React 19: le Actions

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

  1. Migra un form alla volta collegando la tua mutazione a useActionState.
  2. Estrai il bottone di submit e usa useFormStatus per il pending.
  3. Ritorna errori strutturati dall’Action e rendili accessibili (ARIA).
  4. Valuta formAction per bottoni con esiti diversi.
  5. 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.

Torna su