È uscito il Corso Java Completo
Torna al blog

Gestione dello stato in React: cosa usare nel 2026

Guida alla gestione dello stato in React nel 2026: stato del server, del client, dell'URL e dei form, con la scala da useState a Context fino alle librerie.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura

Il progetto cresce, i useState si moltiplicano, e a un certo punto ti fermi a cercare su Google "quale libreria di state management usare". È la domanda sbagliata, ed è il motivo per cui quella ricerca non ti dà mai una risposta soddisfacente. In questo articolo trovi la domanda giusta, i quattro tipi di stato che quasi nessuno distingue, e una scala di soluzioni da salire solo quando serve davvero.

La domanda giusta non è "quale libreria"

La domanda giusta è: che tipo di stato è questo? Perché tipi diversi di stato hanno problemi diversi, e uno strumento pensato per un tipo è inadatto agli altri.

È un cambio di prospettiva piccolo e le conseguenze sono enormi. Quando metti tutto nello stesso contenitore — dati presi dall'API, il menu laterale aperto, i filtri della tabella, il form a metà — poi ti servono strumenti sempre più complicati per tenere in ordine un mucchio che non doveva esistere.

Se hai chiaro cosa sono componenti, stato e props, il resto scorre. Se non lo è, il punto di partenza è cos'è React: lì trovi useState, l'idea che l'interfaccia è una funzione dello stato e perché useEffect non è il posto dove calcolare le cose. Questo articolo dà quella base per scontata.

I quattro tipi di stato

1. Stato del server

Sono i dati che vivono altrove — in un database, dietro una API — e di cui tu tieni in pagina una copia temporanea. L'elenco dei prodotti, il profilo dell'utente, gli ordini, i commenti.

Questa è la maggior parte di ciò che la gente chiama "stato dell'applicazione", e non è affatto stato tuo. È una cache. Ha proprietà che lo stato normale non ha: può essere vecchia, può essere condivisa tra componenti che non si conoscono, può arrivare fuori ordine se due richieste si accavallano, e qualcun altro può modificarla mentre tu la guardi.

Trattarla come stato locale — useState più useEffect più fetch — è la sorgente numero uno dei problemi che poi si cerca di curare con una libreria di stato globale.

2. Stato del client

Roba che esiste solo nel browser, adesso, e a nessun altro interessa: un menu aperto, una modale, quale scheda è attiva, la modalità scura, un accordion espanso.

È lo stato vero e proprio. Di solito è poco, spesso appartiene a un componente solo, e raramente richiede strumenti.

3. Stato dell'URL

Filtri, ordinamento, numero di pagina, termine di ricerca, quale elemento è selezionato. Questi vanno nella query string, non in memoria. È l'errore classico e costa caro.

Se il filtro "categoria = scarpe, ordina per prezzo, pagina 3" sta in un useState, succede questo: l'utente non può condividere il link con quello che sta guardando, il tasto indietro non torna al filtro precedente ma esce dalla pagina, e un ricaricamento azzera tutto. Tre bug che nessuno segnala come bug, ma che rendono l'applicazione irritante.

// no: il filtro esiste solo in memoria
const [categoria, setCategoria] = useState("tutte");

// sì: il filtro sta nell'URL, è condivisibile e sopravvive al reload
const searchParams = useSearchParams();
const router = useRouter();
const categoria = searchParams.get("categoria") ?? "tutte";

function cambiaCategoria(valore) {
  const params = new URLSearchParams(searchParams);
  params.set("categoria", valore);
  router.push(`?${params.toString()}`);
}

Il codice sopra è Next.js, ma il concetto vale ovunque: l'URL è già un contenitore di stato, gratuito, persistente e condivisibile. Non ignorarlo.

4. Stato del form

Il contenuto dei campi mentre l'utente scrive, quali campi ha toccato, gli errori di validazione, se l'invio è in corso.

È stato del client, ma con abbastanza regole proprie da meritare una categoria e, spesso, uno strumento dedicato. Un form con tre campi si scrive a mano; un form con venti campi, validazione condizionale e array dinamici, no.

La metà dei problemi sparisce così

Riassumo il punto perché è tutto l'articolo in due righe.

Quando smetti di trattare i dati del server come stato locale, la maggior parte dei problemi di "state management" non si presenta proprio. Non hai più bisogno di condividere globalmente l'elenco degli utenti fra pagine lontane, perché non è tuo: lo chiedi quando ti serve e una cache te lo restituisce senza rifare la richiesta. Non hai più il problema di "aggiornare lo stato dopo la modifica in tre punti", perché invalidi una chiave e chi la usa si riallinea.

Quello che resta — i menu, le schede, un carrello prima del checkout, le preferenze di visualizzazione — è pochissimo. E per pochissimo stato client, useState basta quasi sempre.

La scala delle soluzioni

Sali un gradino solo quando quello sotto ti ha fatto male davvero. Saltare in cima è il modo migliore per portarsi in casa complessità che non ripaga.

Gradino 1: useState nel componente

Lo stato vive dove viene usato. Nessuna configurazione, nessuna dipendenza, nessun file in più.

La maggior parte dei componenti si ferma qui, e va benissimo così.

Gradino 2: sollevare lo stato

Due componenti fratelli devono vedere lo stesso valore. Lo sposti nel loro genitore comune e lo passi giù come props.

function Pannello() {
  const [schedaAttiva, setSchedaAttiva] = useState("dettagli");

  return (
    <>
      <Schede attiva={schedaAttiva} onCambia={setSchedaAttiva} />
      <Contenuto scheda={schedaAttiva} />
    </>
  );
}

Funziona, è esplicito, è tracciabile. Il limite arriva quando il valore deve scendere di cinque livelli e i componenti in mezzo lo passano avanti senza usarlo. Si chiama prop drilling e diventa fastidioso, non pericoloso: prima di reagire, controlla se non basta comporre meglio i componenti passando children.

Gradino 3: Context

Context fa arrivare un valore a qualsiasi discendente senza passarlo di mano in mano.

const TemaContext = createContext(null);

export function TemaProvider({ children }) {
  const [tema, setTema] = useState("chiaro");
  const valore = useMemo(() => ({ tema, setTema }), [tema]);

  return <TemaContext.Provider value={valore}>{children}</TemaContext.Provider>;
}

export function useTema() {
  const ctx = useContext(TemaContext);
  if (!ctx) throw new Error("useTema va usato dentro TemaProvider");
  return ctx;
}

Il limite reale di Context, quello che conta, è uno: quando il valore cambia, ogni componente che consuma quel context si rirenderizza. Tutti. Anche quelli a cui interessa solo un pezzo del valore.

Con un tema o una lingua non te ne accorgi mai, perché cambiano una volta ogni tanto. Con un oggetto che contiene dieci campi e cambia a ogni battuta sulla tastiera, mezza applicazione si ricalcola per un carattere.

Due precisazioni oneste. La prima: un rirender non è di per sé un disastro, e "ho messo tutto in Context e va benissimo" è una frase vera in molti progetti. La seconda: puoi mitigare dividendo il context in più context per frequenza di cambiamento — uno per i valori stabili, uno per quelli che si muovono. Quando ti accorgi che ne stai creando cinque per convivere con questo problema, sei pronto per il gradino successivo.

Gradino 4: una libreria

Vale la pena prendere una libreria quando ricorre almeno una di queste situazioni:

  • stato client davvero globale, letto e scritto da parti dell'app che non condividono un genitore vicino;
  • aggiornamenti frequenti con molti lettori, dove i rirender di Context si sentono;
  • logica di aggiornamento non banale, che ha senso tenere fuori dai componenti e testare da sola;
  • team numeroso, dove una convenzione unica vale più dell'eleganza.

Non serve perché "l'app è grossa". Un'applicazione grossa fatta soprattutto di dati del server può non avere nessuno stato globale client.

Tabella: tipo di stato, strumento consigliato

Tipo di statoEsempiStrumento
ServerElenchi, profilo, ordini, commentiTanStack Query, oppure i Server Component di Next.js
URLFiltri, ricerca, pagina, ordinamentoQuery string (useSearchParams)
Client localeMenu aperto, modale, accordionuseState
Client condiviso, raroTema, lingua, utente autenticatoContext
Client globale, frequenteCarrello, editor, stato di una UI complessaZustand o simili
FormCampi, validazione, invioReact Hook Form, o a mano se è piccolo

Gli errori che fanno perdere ore

Mettere i dati del server in uno store globale. Li scarichi, li salvi in Zustand o in Redux, e da quel momento sei tu responsabile di aggiornarli, invalidarli e tenerli coerenti. Hai riscritto a mano una cache, male.

Stato duplicato invece che derivato. Se hai la lista, il totale non è stato: è un calcolo. Vale per i filtri applicati, i contatori, "quanti selezionati". Il tema è trattato in cos'è React ed è la causa singola più frequente di dati incoerenti.

Un unico store globale con tutto dentro. Nasce ordinato e diventa un contenitore dove ogni componente pesca a caso. Dopo sei mesi nessuno sa più chi scrive cosa, e togliere un campo è un'operazione di archeologia.

Sollevare lo stato fino alla radice per abitudine. Se lo usa un componente solo, tienilo lì. Lo stato che sale non scende mai più.

Sincronizzare le props nello stato con useEffect. Copiare una prop in un useState per poterla modificare produce due fonti di verità che divergono. Quasi sempre la soluzione è una key sul componente, o calcolare durante il render.

Prendere Redux perché "è lo standard". Redux Toolkit è molto meno verboso di quello che ricordi, e su team grandi ha ancora senso. Ma se sei da solo su un progetto di media grandezza, stai pagando struttura che non ti serve. Vale lo stesso ragionamento di quale framework scegliere: lo standard di qualcun altro non è automaticamente il tuo.

Una nota su TypeScript

Con TypeScript la forma dello stato diventa un contratto verificato: se aggiungi un campo allo store, il compilatore ti mostra tutti i punti da aggiornare. Su uno stato condiviso da molti componenti è la differenza tra un refactoring di dieci minuti e un pomeriggio di errori a runtime. Se stai valutando se introdurlo, questo è uno degli argomenti più solidi a favore.

In sintesi

La domanda giusta è "che tipo di stato è questo", non "quale libreria uso". La risposta alla seconda domanda dipende interamente dalla prima.

La maggior parte dello "stato" di un'applicazione è stato del server: una copia di dati che vivono altrove. Trattarlo come stato locale è l'errore d'origine.

Filtri, ricerca e paginazione vanno nell'URL. È persistente, condivisibile e funziona con il tasto indietro senza che tu scriva niente.

Context non è lento: è indiscriminato. Chi lo consuma si rirenderizza quando il valore cambia, e questo conta solo se cambia spesso.

Per la maggior parte dei progetti piccoli non serve nessuna libreria di stato. useState più un paio di Context coprono più casi di quanto si dica in giro.

Il passo successivo dipende da cosa ti manca: per lo stato client globale vedi cos'è Zustand, per i dati del server cos'è TanStack Query.