📋 Tabla de contenidos
-
useState
– El fundamento del estado -
useEffect
– Efectos secundarios y ciclo de vida -
useContext
– Compartir estado sin props -
useReducer
– Lógica de Estado Compleja y predecible -
useRef
– Referencias, Almacenamiento y Escape -
useMemo
yuseCallback
– Optimizando el Rendimiento - Custom Hooks – Creando Lógica Reutilizable
- Errores Comunes y Soluciones
¿Qué es y para qué sirve?
useReducer
es una alternativa a useState
para gestionar lógicas de estado más complejas. Si te encuentras con un useState
que maneja un objeto grande con muchas funciones de actualización diferentes y que se están volviendo difíciles de manejar, useReducer
es la solución.
Centraliza toda la lógica de actualización del estado en una única función llamada "reducer", haciendo que el flujo de datos sea más predecible y fácil de depurar. Es conceptualmente similar a Redux, pero integrado en React.
Úsalo cuando:
- El próximo estado depende de una lógica compleja basada en el estado anterior.
- El estado tiene una estructura compleja (objetos anidados, arrays de objetos).
- La lógica de actualización del estado necesita ser probada de forma aislada.
- Quieres optimizar el rendimiento de componentes que disparan actualizaciones profundas, ya que
dispatch
no cambia entre renders.
Sintaxis y flujo de trabajo
const [state, dispatch] = useReducer(reducer, initialState);
-
reducer
: Una función pura(state, action) => newState
que toma el estado actual y una "acción", y devuelve el nuevo estado. -
initialState
: El estado inicial, similar al deuseState
. -
state
: El estado actual. -
dispatch
: Una función que "despacha" acciones a la funciónreducer
. Es la única forma de desencadenar una actualización de estado. Una acción es típicamente un objeto con una propiedadtype
(ej.{ type: 'INCREMENT' }
).
El flujo es: Componente → dispatch(action)
→ reducer(state, action)
→ Nuevo Estado → Re-renderizado.
Ejemplo práctico detallado: lista de tareas (Todo List)
Este es el ejemplo canónico para useReducer
, ya que implica múltiples tipos de actualizaciones sobre un array de objetos.
1. Definir el Reducer y el Estado Inicial
// components/TodoList.js
// Estado inicial: un array de tareas
const initialState = {
todos: [
{ id: 1, text: 'Aprender useReducer', completed: true },
{ id: 2, text: 'Practicar con un ejemplo', completed: false },
],
};
// Acciones que podemos despachar
export const ACTIONS = {
ADD_TODO: 'add-todo',
TOGGLE_TODO: 'toggle-todo',
DELETE_TODO: 'delete-todo',
};
// El reducer contiene toda la lógica de actualización
function reducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.payload.text, completed: false }],
};
case ACTIONS.TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo
),
};
case ACTIONS.DELETE_TODO:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload.id),
};
default:
return state;
}
}
2. Usar useReducer
en el Componente
import React, { useReducer, useState } from 'react';
// (el código del reducer y el estado inicial va aquí arriba)
function TodoList() {
const [state, dispatch] = useReducer(reducer, initialState);
const [newTodoText, setNewTodoText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (newTodoText.trim() === '') return;
dispatch({ type: ACTIONS.ADD_TODO, payload: { text: newTodoText } });
setNewTodoText('');
};
return (
<div>
<h3>Lista de Tareas con useReducer</h3>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="Añadir nueva tarea"
/>
<button type="submit">Añadir</button>
</form>
<ul>
{state.todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
<button onClick={() => dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })}>
{todo.completed ? 'Undo' : 'Completar'}
</button>
<button onClick={() => dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })}>
Eliminar
</button>
</li>
))}
</ul>
</div>
);
}
✅ Buenas practicas y patrones comunes
- Reducers Puros: Un reducer nunca debe modificar el estado original (
state
) o la acción (action
). Siempre debe devolver un nuevo objeto de estado. Tampoco debe realizar efectos secundarios (como llamadas a API). - Usar
payload
para los datos: Es una convención común pasar los datos necesarios para la actualización dentro de una propiedadpayload
en el objeto de la acción. Ej:{ type: 'ADD', payload: { newItem } }
. - Exportar constantes de acción: Definir los
type
de las acciones como constantes (como en el ejemploACTIONS
) ayuda a evitar errores de tipeo y facilita el mantenimiento. - Separar el reducer: Para componentes muy complejos o para reutilizar la lógica, puedes definir el reducer y el estado inicial en un archivo separado e importarlos.
🚨 Errores comunes y cómo evitarlos
-
Error: Mutar el estado en el reducer.
// MAL ❌ case ACTIONS.DELETE_TODO: // Esto modifica el array original, es un anti-patrón. const index = state.todos.findIndex(t => t.id === action.payload.id); state.todos.splice(index, 1); return state;
- Solución: Siempre retorna una copia nueva del estado.
// BIEN ✅ case ACTIONS.DELETE_TODO: return { ...state, todos: state.todos.filter(t => t.id !== action.payload.id) };
-
Error: Olvidar el
default
case en elswitch
.- Solución: Si tu reducer recibe una acción que no reconoce, debería devolver el estado actual sin modificarlo. El
default: return state;
se encarga de esto. Sin él, tu reducer devolveríaundefined
y tu aplicación se rompería.
- Solución: Si tu reducer recibe una acción que no reconoce, debería devolver el estado actual sin modificarlo. El
🚀 Retos prácticos
- Formulario Complejo: Convierte un formulario con varios campos (
useState
con un objeto) a unuseReducer
. Crea acciones paraUPDATE_FIELD
yRESET_FORM
. - Carrito de Compras: Gestiona un carrito de compras con acciones como
ADD_ITEM
,REMOVE_ITEM
,INCREMENT_QUANTITY
,DECREMENT_QUANTITY
yCLEAR_CART
. - Juego Simple: Crea un juego simple (como un "clicker" con mejoras que puedes comprar) donde el estado del juego (puntos, mejoras, etc.) se gestione con
useReducer
.
Top comments (0)