📋 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 -
useMemoyuseCallback– 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
dispatchno cambia entre renders.
Sintaxis y flujo de trabajo
const [state, dispatch] = useReducer(reducer, initialState);
-
reducer: Una función pura(state, action) => newStateque 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
payloadpara los datos: Es una convención común pasar los datos necesarios para la actualización dentro de una propiedadpayloaden el objeto de la acción. Ej:{ type: 'ADD', payload: { newItem } }. - Exportar constantes de acción: Definir los
typede 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
defaultcase 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íaundefinedy 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 (
useStatecon un objeto) a unuseReducer. Crea acciones paraUPDATE_FIELDyRESET_FORM. - Carrito de Compras: Gestiona un carrito de compras con acciones como
ADD_ITEM,REMOVE_ITEM,INCREMENT_QUANTITY,DECREMENT_QUANTITYyCLEAR_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)