Gestire lo stato globale React è una delle sfide più comuni nello sviluppo di applicazioni complesse. Quando i componenti devono condividere dati, passare props attraverso molteplici livelli (noto come “prop drilling”) diventa inefficiente e difficile da mantenere. Questo articolo esplora le principali soluzioni per creare uno stato centralizzato, confrontando l’approccio nativo con le librerie più popolari.
Il Problema: Il “Prop Drilling”
Immagina un’applicazione React con una struttura di componenti annidati. Se un componente genitore al livello più alto possiede un dato (es. l’utente autenticato) che serve a un componente figlio molto in profondità, quel dato deve essere passato come prop attraverso tutti i componenti intermedi. Questo processo, chiamato prop drilling, rende il codice verboso e accoppia strettamente componenti che non utilizzano direttamente quei dati.
La soluzione è creare uno “store” globale, un contenitore di stato accessibile da qualsiasi componente dell’applicazione senza dover passare props manualmente. Vediamo le opzioni principali.
Context API: La Soluzione Nativa di React
React offre una soluzione integrata per la gestione dello stato globale: la Context API. Introdotta per evitare il prop drilling, permette di creare un “contesto” che espone dati a tutti i componenti discendenti che ne fanno richiesta, utilizzando gli hook createContext e useContext.
Pro e Contro di Context API
- Pro: Soluzione nativa, non richiede dipendenze esterne.
- Pro: Semplice da implementare per casi d’uso non troppo complessi.
- Contro: Può causare ri-render non necessari nei componenti che consumano il contesto, anche se il dato specifico che usano non è cambiato.
- Contro: Meno ottimizzato per aggiornamenti di stato ad alta frequenza rispetto a librerie dedicate.
Esempio pratico con useContext
Prima creiamo un contesto per il tema dell’applicazione.
// ThemeContext.js
import { createContext, useState } from 'react';
export const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
Poi, avvolgiamo la nostra App nel Provider.
// App.js
import { ThemeProvider } from './ThemeContext';
import Toolbar from './Toolbar';
function App() {
return (
<ThemeProvider>
<Toolbar />
</ThemeProvider>
);
}
Infine, un componente può consumare il contesto con l’hook useContext.
// Toolbar.js
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function Toolbar() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
<p>Il tema attuale è: {theme}</p>
<button onClick={toggleTheme}>Cambia Tema</button>
</div>
);
}
Redux: Lo Standard per Applicazioni Complesse
Redux è una libreria esterna che implementa un pattern prevedibile per la gestione dello stato. Si basa su tre principi fondamentali: una singola fonte di verità (lo store), stato di sola lettura (modificabile solo tramite “azioni”) e modifiche effettuate con funzioni pure (i “reducers”).
Oggi, l’approccio consigliato è usare Redux Toolkit, che semplifica notevolmente la configurazione e riduce il codice boilerplate.
Pro e Contro di Redux
- Pro: Estremamente prevedibile e facile da debuggare grazie ai Redux DevTools.
- Pro: Ottimizzato per performance elevate e aggiornamenti frequenti.
- Pro: Vasto ecosistema di middleware (es. Redux Thunk, Redux Saga).
- Contro: Curva di apprendimento più ripida rispetto a Context API.
- Contro: Può risultare eccessivo per progetti di piccole dimensioni.
Esempio pratico con Redux Toolkit
Definiamo una “slice” dello stato che contiene il reducer e le azioni relative a un contatore.
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Un componente può interagire con lo store usando gli hook useSelector e useDispatch.
// Counter.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './features/counter/counterSlice';
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment())}>Incrementa</button>
<span>{count}</span>
<button onClick={() => dispatch(decrement())}>Decrementa</button>
</div>
);
}
Alternative Moderne: Zustand e Altre
Negli ultimi anni sono emerse librerie più leggere e minimaliste come Zustand, Jotai e Recoil. Zustand, in particolare, ha guadagnato popolarità per la sua semplicità e per un’API basata sugli React Hooks che riduce drasticamente il boilerplate.
Zustand unisce la semplicità di Context con le performance ottimizzate di Redux, evitando il problema dei ri-render non necessari.
Esempio pratico con Zustand
Creare uno store in Zustand richiede poche righe di codice.
// store.js
import create from 'zustand';
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
export default useStore;
Usarlo in un componente è ancora più semplice.
// BearCounter.js
import useStore from './store';
function BearCounter() {
const bears = useStore((state) => state.bears);
return <h1>{bears} orsi intorno.</h1>;
}
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation);
return <button onClick={increasePopulation}>Aggiungi un orso</button>;
}
Quale soluzione scegliere per lo stato globale React?
La scelta della giusta soluzione per la gestione dello stato globale React dipende dalle esigenze specifiche del progetto:
- Context API: Ideale per applicazioni piccole o per gestire stati semplici e con pochi aggiornamenti (es. tema, lingua, utente autenticato).
- Redux: La scelta più solida per applicazioni di grandi dimensioni, con logiche di stato complesse, e dove la prevedibilità e il debugging sono critici.
- Zustand: Un’ottima via di mezzo. Offre un’esperienza di sviluppo rapida e performance elevate, perfetta per progetti di medie dimensioni o per chi cerca un’alternativa meno verbosa a Redux.
Analizzare la complessità e la scala futura della tua applicazione è il primo passo per implementare una gestione dello stato robusta, manutenibile ed efficiente.





