React
Context e performance: cosa causa i re-render
Il context non ri-renderizza tutta l’app: ri-renderizza i consumatori quando il valore cambia identità, e la causa più comune è un oggetto creato nel provider.
In sintesi
- Si ri-renderizzano solo i componenti che leggono il context, non tutto l’albero.
- Un valore creato nel render del provider cambia identità a ogni render e sveglia ogni consumatore.
- Separare stato e azioni in due context riduce i render senza aggiungere librerie.
Il context non ri-renderizza tutta l’app: ri-renderizza i componenti che leggono quel context quando il valore cambia identità. Il costo nasce dal valore ricreato a ogni render del provider, non dal context in sé.
Chi si ri-renderizza
Un componente che chiama useContext si iscrive al context. Quando il provider passa un valore diverso, React ri-renderizza tutti i consumatori, anche quelli che leggono solo una parte del valore. I componenti che non leggono il context non vengono toccati.
Il valore che cambia identità
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
// new object on every render: every consumer re-renders
const value = { theme, setTheme };
return (
{children}
);
}
Il provider si ri-renderizza a ogni cambio di tema e crea un oggetto nuovo: il confronto fallisce e ogni consumatore si ri-renderizza anche se legge solo setTheme. Con useMemo sul valore l’oggetto resta lo stesso finché theme non cambia.
Separare i context
La seconda correzione è dividere stato e azioni: un context per il valore che cambia e uno per le funzioni stabili. I componenti che leggono solo le azioni smettono di ri-renderizzarsi quando il tema cambia.
Un provider, molti figli
Anche i figli del provider si ri-renderizzano quando il provider stesso si ri-renderizza, a meno che non siano memoizzati: il context non è l’unica causa. Prima di aggiungere memo ovunque, controlla se il provider sta ricreando il valore.
Osservarlo
npm create vite@latest context-demo -- --template react-ts
cd context-demo
npm install
npm run dev
Con l’app aperta, il Profiler di React DevTools registra un’interazione e mostra quali componenti si sono ri-renderizzati e perché. È il modo più rapido per distinguere un problema di context da un problema di props.
Record vivi nel catalogo
Due record che vivono di context: sidebar, dove lo stato di apertura passa da un provider ai suoi figli, e theme-toggle, che legge e scrive il tema. Il resto sta tra i 111.985 componenti e i 1.556 blocchi.
La limitazione
Separare i context riduce i re-render ma aggiunge provider annidati: ogni livello è un componente in più e un ordine da rispettare. E nessuna delle due correzioni aiuta se il valore letto cambia davvero a ogni interazione: lì il problema è nel dato, non nel context.
Dove continuare
Il componente memoizzato che completa la correzione è in React.memo; il valore calcolato invece che copiato è in stato derivato; il confronto fra le librerie headless che usano questo schema è in Radix vs Base UI vs React Aria.
Domande frequenti
Il context causa il re-render di tutti i componenti?
No. Si ri-renderizzano solo i componenti che chiamano useContext su quel context e che stanno sotto il provider. Un componente che non legge il context non riceve l’aggiornamento.
Meglio context o passare le props?
Le props restano la scelta giusta per due o tre livelli; il context conviene quando lo stesso valore serve a molti componenti distanti. Il context non elimina i re-render, cambia solo il modo in cui il valore arriva.
Il context sostituisce Redux o Zustand?
No. Il context distribuisce un valore, non gestisce selezioni, cache o aggiornamenti parziali: ogni cambio ri-renderizza tutti i consumatori. Per uno stato complesso servono store con selettori.
Perché il provider ri-renderizza anche i figli?
Perché i figli sono parte del render del provider, indipendentemente dal context. Se il provider ricrea il valore o il suo stato interno cambia, i figli si ri-renderizzano salvo memoizzazione.
useMemo sul valore del provider è obbligatorio?
No. Serve quando il provider ha consumatori costosi e si ri-renderizza spesso. Su un provider che cambia una volta al minuto la differenza non si misura e il memo aggiunge solo codice.