React

Stato derivato: smettere di copiare le props

Lo stato derivato è un valore che si calcola da props o da altro stato: copiarlo in useState crea una seconda fonte di verità che prima o poi diverge.

In sintesi

  • Un valore calcolabile durante il render non ha bisogno di useState né di un effect di sincronizzazione.
  • Copiare le props in stato produce un render in più e un frame con il valore vecchio.
  • Per ripartire da zero quando cambia l’entità basta cambiare la key del componente, senza effect.

Lo stato derivato è un valore che si calcola dalle props o da altro stato. Se lo copi in useState crei una seconda fonte di verità, che resta indietro appena le props cambiano.

Il sintomo

Il codice tipico è un useEffect che copia una prop nello stato ogni volta che la prop cambia. Funziona finché la prop non cambia due volte nello stesso commit, e costa un render extra: il primo mostra il valore vecchio, il secondo quello nuovo.

La correzione

Se il valore si può calcolare durante il render, calcolalo: nessun effect, nessun render extra, nessuno stato da sincronizzare.

function SearchList({ items, query }) {
  const visible = items.filter((item) =>
    item.name.toLowerCase().includes(query.toLowerCase()),
  );

  return (
    
      {visible.map((item) => (
        {item.name}
      ))}
    
  );
}

Quando lo stato serve davvero

  • Input dell’utente: un campo scrive in stato, non deriva da una prop.
  • Reset per entità: quando la prop identifica un’altra entità, cambia la key del componente e lo stato riparte da zero, senza effect.
  • Valore iniziale: se serve solo il primo valore della prop, passalo a useState e non riscriverlo dopo.

Il costo del calcolo

Filtrare mille elementi a ogni tasto è lavoro inutile solo se il render è frequente: prima misura, poi valuta useMemo. La memoizzazione aggiunge una lista di dipendenze da mantenere, e sbagliarla produce lo stesso difetto dello stato copiato, con meno indizi.

Provarlo con i tipi

npm create vite@latest derived-demo -- --template react-ts
cd derived-demo
npm install
npx tsc --noEmit

Il template react-ts di Vite, con licenza MIT, include React 19 e TypeScript; npx tsc --noEmit controlla i tipi senza produrre file e segnala una prop che manca nella derivazione.

Due record dove la derivazione è la norma: filters, che filtra una lista da valori di form, e table, che ordina le righe in base allo stato corrente. Il resto sta tra i 111.985 componenti e i 1.556 blocchi.

La limitazione

Calcolare durante il render funziona solo se il calcolo è puro e non tocca il DOM: una derivazione che legge misure o dati esterni va in un effect. E un calcolo pesante ripetuto a ogni render è un problema di prestazioni, non di architettura.

Dove continuare

L’ordine delle chiamate è in regole degli hook; la funzione stabile da passare ai figli è in useCallback; il confronto con un framework a stato reattivo è in Svelte vs React.

Domande frequenti

Stato derivato e valore calcolato sono la stessa cosa?

Sì. Lo stato derivato è un valore che dipende da altre fonti e che quindi non ha bisogno di essere salvato: si ricalcola durante il render, ogni volta che le fonti cambiano.

Posso sincronizzare una prop con useState in un effect?

Tecnicamente sì, ma è il pattern che il team di React sconsiglia: produce un render in più e può mostrare un valore vecchio. Se il valore è calcolabile, calcolalo; se serve un reset, cambia la key.

Come resetto lo stato quando cambia una prop?

Con la key. Metti key={entity.id} sul componente che contiene lo stato: React lo smonta e lo rimonta quando l’id cambia, e lo stato riparte dai valori iniziali senza un effect.

useMemo sostituisce lo stato derivato?

No, lo ottimizza. Lo stato derivato è la scelta di non salvare il valore; useMemo è una cache opzionale per non ricalcolarlo a ogni render. Prima si toglie lo stato, poi si aggiunge la cache.

Derivare a ogni render è lento?

Dipende dal calcolo e dalla frequenza. Un filtro su cento elementi è irrilevante; un ordinamento su diecimila righe a ogni tasto si misura nel Profiler prima di aggiungere una cache.