Estructura el estado complejo con un reducer puro que centraliza las transiciones y descarta las combinaciones imposibles.
Abrir esta lección en KodokonEn cuanto un componente orquesta varios valores de estado que evolucionan juntos - el estado de una petición, unos datos, un error - multiplicar las llamadas a useState dispersa la lógica de transición por cada manejador de eventos. Ya conoces el síntoma: tres setters que hay que llamar en el orden correcto y, tarde o temprano, un estado inconsistente, como un estado success junto a un error que no es nulo. useReducer invierte la responsabilidad: el estado se convierte en un único objeto y cada cambio pasa por una acción interpretada por una función central, el reducer.
function requestReducer(state, action) {
switch (action.type) {
case "start":
return { status: "loading", data: null, error: null };
case "resolve":
return {
status: "success",
data: action.data,
error: null,
};
case "reject":
return {
status: "error",
data: null,
error: action.error,
};
default:
throw new Error("Unknown action: " + action.type);
}
}El reducer es una función pura: recibe el estado actual y una acción, y luego devuelve el siguiente estado, sin efectos secundarios. Los beneficios son inmediatos: las transiciones son explícitas y exhaustivas, los estados imposibles desaparecen de raíz (cada case reconstruye un objeto consistente) y la lógica se lee en un solo sitio en lugar de estar dispersa. Una señal de alarma útil: si te encuentras copiando y pegando la misma secuencia de llamadas a setState en dos sitios, probablemente te falte un reducer.
function UserProfile() {
const [state, dispatch] = useReducer(requestReducer, {
status: "idle",
data: null,
error: null,
});
async function load(id) {
dispatch({ type: "start" });
try {
const res = await fetch("/api/users/" + id);
const data = await res.json();
dispatch({ type: "resolve", data });
} catch (error) {
dispatch({ type: "reject", error });
}
}
return <button onClick={() => load(1)}>Load</button>;
}Elegir entre useState y useReducer no es una cuestión de gusto. Reserva useState para valores simples e independientes; cambia a useReducer cuando el siguiente estado dependa del anterior o cuando varios campos cambien juntos. Dos ventajas adicionales: el tercer argumento aporta inicialización perezosa (useReducer(reducer, arg, init) solo ejecuta init en el primer render), y React garantiza que dispatch conserva la misma identidad de un render al siguiente - puedes pasarlo a los hijos sin useCallback.