Sviluppatore davanti a un grande schermo con diagrammi di stato globale React e librerie collegate

Stato globale React: opzioni e librerie

Quando l’app cresce, lo stato diventa ingestibile se non hai una strategia. Qui trovi un confronto guidato tra Context, Redux e altre soluzioni per decidere con lucidità.

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.