📦 ¿Qué es useReducer?
Es un hook que te permite manejar estados complejos o con múltiples transiciones de forma predecible y organizada.
✅ Úsalo cuando:
- Tienes múltiples subvalores en el estado
- El siguiente estado depende del anterior
- Quieres mover la lógica de actualización a una función separada
🧪 Sintaxis básica
const [state, dispatch] = useReducer(reducer, initialState);
- reducer: una función (state, action) => newState
- initialState: estado inicial
- dispatch: función que usas para lanzar acciones
🧠 ¿Qué es un reducer en JavaScript?
Antes de seguir aventurándonos en el useReducer, conozcamos las bases.
Un reducer es una función que toma una colección de elementos (como un array) y los reduce a un único valor, he ahí el nombre de reducer (reductor en español).
🧪 Sintaxis básicas
Se tiene 2 valores, un callback y un valor inicial .
const resultado = array.reduce(callback, valorInicial)
Sintaxis del callback
(acumulador, valorActual) => {}
- acumulador: Guardará en un inicio el valor inicial que se le paso al reduce, y luego según se opere.
- valorActual: El elemento actual del array que estamos procesando
Ejemplo
const numeros = [1, 2, 3, 4];
const suma = numeros.reduce((acumulador, valorActual) => {
return acumulador + valorActual;
}, 0); // El 0 es el valor inicial del acumulador
console.log(suma); // 10
Y el .reduce()
hace algo así internamente:
acumulador = 0 (valor inicial)
1 → acumulador = 0 + 1 = 1
2 → acumulador = 1 + 2 = 3
3 → acumulador = 3 + 3 = 6
4 → acumulador = 6 + 4 = 10
Entendiendo esto, podemos pasar a:
¿Cómo puedo aplicar el useReducer en mi proyecto?
Imaginemos que tenemos un formulario con 3 pasos:
- Información personal (name, email)
- Dirección (country, city)
- Revisión y envío
Vamos a controlar:
- Los datos del formulario,
- El paso actual,
- Acciones para avanzar, retroceder o actualizar datos.
🔧 Lógica del reducer y tipado
// 🧱 Tipos del estado
type FormData = {
name: string;
email: string;
country: string;
city: string;
};
type State = {
currentStep: number;
data: FormData;
};
// 🎯 Tipos de acciones
type Action =
| { type: 'NEXT_STEP' }
| { type: 'PREV_STEP' }
| { type: 'UPDATE_FIELD'; field: keyof FormData; value: string }
| { type: 'RESET_FORM' };
// 🎛️ Estado inicial
const initialState: State = {
currentStep: 0,
data: {
name: '',
email: '',
country: '',
city: '',
},
};
// 🔄 Reducer
function formReducer(state: State, action: Action): State {
switch (action.type) {
case 'NEXT_STEP':
return { ...state, currentStep: state.currentStep + 1 };
case 'PREV_STEP':
return { ...state, currentStep: state.currentStep - 1 };
case 'UPDATE_FIELD':
return {
...state,
data: { ...state.data, [action.field]: action.value },
};
case 'RESET_FORM':
return initialState;
default:
return state;
}
}
🧠 ¿Cómo se usa en un componente?
const [state, dispatch] = useReducer(formReducer, initialState);
// Para avanzar
dispatch({ type: 'NEXT_STEP' });
// Para retroceder
dispatch({ type: 'PREV_STEP' });
// Para actualizar un campo
dispatch({ type: 'UPDATE_FIELD', field: 'email', value: '[email protected]' });
// Para reiniciar el formulario
dispatch({ type: 'RESET_FORM' });
🧰 Bonus: 3er parámetro de useReducer
Este tercer parámetro es un parámetro opcional
const [state, dispatch] = useReducer(reducer, initialArg, init?);
-
reducer
: la función reductora -
initialArg
: valor inicial del estado, que puede ser un estado directo o un argumento para construirlo -
init
(opcional): una función que inicializa el estado a partir deinitialArg
🤔 ¿Para qué sirve el tercer parámetro?
Sirve para calcular el estado inicial de forma perezosa (lazy), es decir, solo se ejecuta una vez, al montar el componente.
Esto es útil cuando:
- El estado inicial requiere cálculos pesados
- Quieres construir el estado inicial desde una prop o valor externo
- Quieres usar algo como JSON.parse(localStorage.getItem(...))
🧪 Ejemplo: Cargar estado desde localStorage
type State = { theme: 'light' | 'dark' };
function reducer(state: State, action: { type: 'TOGGLE_THEME' }): State {
switch (action.type) {
case 'TOGGLE_THEME':
return { theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
// Función init: se ejecuta una vez para calcular el estado inicial
function init(initialArg: string | null): State {
const parsed = initialArg ? JSON.parse(initialArg) : null;
return parsed ?? { theme: 'light' };
}
const [state, dispatch] = useReducer(reducer, localStorage.getItem('theme'), init);
🧠 Casos de uso comunes
- Formularios con validación compleja
- Manejo de múltiples estados relacionados
- UIs con múltiples interacciones o pasos (wizards, tabs, etc.)