useReducer
useReducer è un Hook di React utilizzato per gestire stati complessi o logiche di aggiornamento articolate, ed è spesso un’alternativa più strutturata a useState. È particolarmente utile quando lo stato dipende da più azioni, quando contiene più valori correlati o quando la logica di aggiornamento deve essere centralizzata e facilmente manutenibile.
Cos’è useReducer
useReducer si ispira direttamente al pattern Reducer molto diffuso in JavaScript e in librerie come Redux. L’idea di base è separare:
- lo stato
- le azioni che descrivono cosa deve succedere
- la logica che decide come lo stato cambia
In questo modo il codice risulta più prevedibile, testabile e scalabile.
Sintassi di base
const [state, dispatch] = useReducer(reducer, initialState);state: lo stato correntedispatch: funzione usata per inviare azionireducer: funzione pura che gestisce gli aggiornamenti dello statoinitialState: valore iniziale dello stato
Il reducer
Il reducer è una funzione che riceve due argomenti:
function reducer(state, action) {
return newState;
}state: lo stato attualeaction: un oggetto che descrive l’azione da eseguire
Il reducer non deve mai modificare direttamente lo stato, ma restituire un nuovo stato.
Esempio di reducer
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
return state;
}
}Utilizzo di useReducer in un componente
import { useReducer } from "react";
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Valore: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Incrementa</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrementa</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}In questo esempio:
- il componente non conosce la logica di aggiornamento dello stato
- invia solo azioni descrittive
- il reducer decide come aggiornare lo stato
Struttura delle azioni
Le azioni sono semplici oggetti JavaScript. La proprietà type è una convenzione, ma è fortemente consigliata.
dispatch({
type: "increment",
payload: 5,
});Nel reducer:
case "increment":
return { count: state.count + action.payload };Questo approccio rende il codice più estensibile e leggibile.
Stato complesso con useReducer
useReducer è ideale quando lo stato è un oggetto con più proprietà correlate.
const initialState = {
loading: false,
data: null,
error: null,
};Reducer:
function dataReducer(state, action) {
switch (action.type) {
case "fetch_start":
return { ...state, loading: true, error: null };
case "fetch_success":
return { loading: false, data: action.payload, error: null };
case "fetch_error":
return { loading: false, data: null, error: action.payload };
default:
return state;
}
}Questo pattern è molto comune nella gestione di chiamate API.
useReducer vs useState
Quando preferire useReducer
- Stato complesso o annidato
- Molte azioni diverse che modificano lo stato
- Logica di aggiornamento articolata
- Necessità di codice più prevedibile e testabile
- Condivisione della stessa logica di stato tra più componenti
Quando useState è sufficiente
- Stato semplice
- Aggiornamenti diretti e poco complessi
- Componenti piccoli o isolati
Integrazione con useContext
useReducer viene spesso usato insieme a useContext per creare una gestione globale dello stato senza librerie esterne.
Pattern comune:
useReducergestisce lo statoContextesponestateedispatch
Questo approccio è adatto a progetti di media complessità.
Inizializzazione pigra (lazy initialization)
Per stati iniziali costosi da calcolare è possibile usare una funzione di inizializzazione:
const [state, dispatch] = useReducer(reducer, initialArg, init);
function init(initialArg) {
return { count: initialArg };
}La funzione init viene eseguita solo una volta al primo render.
Best practice
- Mantieni i reducer puri (niente effetti collaterali)
- Usa
switcho mappe di funzioni per chiarezza - Centralizza la logica di aggiornamento nello stesso file
- Dai nomi chiari alle azioni
- Evita reducer troppo grandi: suddividi la logica se necessario
Conclusione
useReducer è uno strumento potente per gestire stati complessi in React. Favorisce una separazione chiara delle responsabilità, migliora la leggibilità del codice e rende le applicazioni più scalabili. Anche se può sembrare più verboso rispetto a useState, diventa rapidamente indispensabile man mano che la complessità dell’applicazione cresce.