Hook: useReducer
Introducción
useReducer es un hook en React que proporciona una forma más avanzada de manejar el estado, ideal para aplicaciones complejas donde se necesitan múltiples actualizaciones de estado relacionadas o lógicas más estructuradas. Es similar a cómo funcionan los reducers en Redux.
1. Qué es useReducer? #
useReducer gestiona el estado de un componente mediante un reducer, que es una función que recibe el estado actual y una acción, y devuelve un nuevo estado.
Sintaxis #
1const [estado, dispatch] = useReducer(reducer, estadoInicial);
reducer: Una función que determina cómo cambia el estado según la acción.estadoInicial: El valor inicial del estado.dispatch: Una función para enviar acciones al reducer.
2. Ejemplo Básico: Contador #
Código #
1import React, { useReducer } from "react"; 2 3const estadoInicial = { contador: 0 }; 4 5function reducer(estado, accion) { 6 switch (accion.type) { 7 case "incrementar": 8 return { contador: estado.contador + 1 }; 9 case "decrementar": 10 return { contador: estado.contador - 1 }; 11 case "reiniciar": 12 return estadoInicial; 13 default: 14 throw new Error("Acción no reconocida"); 15 } 16} 17 18function Contador() { 19 const [estado, dispatch] = useReducer(reducer, estadoInicial); 20 21 return ( 22 <div> 23 <p>Contador: {estado.contador}</p> 24 <button onClick={() => dispatch({ type: "incrementar" })}> 25 Incrementar 26 </button> 27 <button onClick={() => dispatch({ type: "decrementar" })}> 28 Decrementar 29 </button> 30 <button onClick={() => dispatch({ type: "reiniciar" })}>Reiniciar</button> 31 </div> 32 ); 33} 34 35export default Contador;
Explicación #
estadoInicial: Define el estado inicial del contador.reducer: Maneja las acciones (incrementar,decrementar,reiniciar) y devuelve el nuevo estado.dispatch: Envía una acción al reducer para actualizar el estado.
3. Ventajas de useReducer Sobre useState #
- Estructura: Maneja estados complejos de manera más predecible.
- Escalabilidad: Ideal para lógica de estado complicada o relacionada.
- Agrupación de Actualizaciones: Permite combinar varias acciones en un solo reducer.
4. Ejemplo: Manejo de Formularios #
useReducer puede ser útil para manejar formularios con múltiples campos y lógica de validación.
Código #
1import React, { useReducer } from "react"; 2 3const estadoInicial = { 4 nombre: "", 5 correo: "", 6 errores: {}, 7}; 8 9function reducer(estado, accion) { 10 switch (accion.type) { 11 case "actualizar_campo": 12 return { 13 ...estado, 14 [accion.campo]: accion.valor, 15 }; 16 case "validar": 17 const errores = {}; 18 if (!estado.nombre) errores.nombre = "El nombre es obligatorio"; 19 if (!estado.correo.includes("@")) errores.correo = "Correo no válido"; 20 return { 21 ...estado, 22 errores, 23 }; 24 default: 25 throw new Error("Acción no reconocida"); 26 } 27} 28 29function Formulario() { 30 const [estado, dispatch] = useReducer(reducer, estadoInicial); 31 32 const manejarEnvio = (e) => { 33 e.preventDefault(); 34 dispatch({ type: "validar" }); 35 36 if (Object.keys(estado.errores).length === 0) { 37 alert("Formulario enviado correctamente"); 38 } 39 }; 40 41 return ( 42 <form onSubmit={manejarEnvio}> 43 <div> 44 <label> 45 Nombre: 46 <input 47 type="text" 48 value={estado.nombre} 49 onChange={(e) => 50 dispatch({ 51 type: "actualizar_campo", 52 campo: "nombre", 53 valor: e.target.value, 54 }) 55 } 56 /> 57 </label> 58 {estado.errores.nombre && <p style={{ color: "red" }}>{estado.errores.nombre}</p>} 59 </div> 60 <div> 61 <label> 62 Correo: 63 <input 64 type="email" 65 value={estado.correo} 66 onChange={(e) => 67 dispatch({ 68 type: "actualizar_campo", 69 campo: "correo", 70 valor: e.target.value, 71 }) 72 } 73 /> 74 </label> 75 {estado.errores.correo && <p style={{ color: "red" }}>{estado.errores.correo}</p>} 76 </div> 77 <button type="submit">Enviar</button> 78 </form> 79 ); 80} 81 82export default Formulario;
Explicación #
estadoInicial: Contiene los valores iniciales del formulario.reducer:actualizar_campo: Actualiza el valor de un campo específico.validar: Realiza validaciones y actualiza los errores.
- Errores: Se muestran mensajes si no se cumplen las condiciones de validación.
5. Ejemplo: Lista de Tareas (To-Do List) #
Código #
1import React, { useReducer } from "react"; 2 3const estadoInicial = { tareas: [] }; 4 5function reducer(estado, accion) { 6 switch (accion.type) { 7 case "agregar_tarea": 8 return { 9 ...estado, 10 tareas: [...estado.tareas, { texto: accion.texto, completada: false }], 11 }; 12 case "eliminar_tarea": 13 return { 14 ...estado, 15 tareas: estado.tareas.filter((_, index) => index !== accion.index), 16 }; 17 case "toggle_tarea": 18 return { 19 ...estado, 20 tareas: estado.tareas.map((tarea, index) => 21 index === accion.index 22 ? { ...tarea, completada: !tarea.completada } 23 : tarea 24 ), 25 }; 26 default: 27 throw new Error("Acción no reconocida"); 28 } 29} 30 31function ListaTareas() { 32 const [estado, dispatch] = useReducer(reducer, estadoInicial); 33 const [texto, setTexto] = React.useState(""); 34 35 const manejarEnvio = (e) => { 36 e.preventDefault(); 37 if (texto.trim() === "") return; 38 dispatch({ type: "agregar_tarea", texto }); 39 setTexto(""); 40 }; 41 42 return ( 43 <div> 44 <h1>Lista de Tareas</h1> 45 <form onSubmit={manejarEnvio}> 46 <input 47 type="text" 48 value={texto} 49 onChange={(e) => setTexto(e.target.value)} 50 placeholder="Nueva tarea" 51 /> 52 <button type="submit">Agregar</button> 53 </form> 54 <ul> 55 {estado.tareas.map((tarea, index) => ( 56 <li key={index}> 57 <span 58 style={{ 59 textDecoration: tarea.completada ? "line-through" : "none", 60 }} 61 > 62 {tarea.texto} 63 </span> 64 <button onClick={() => dispatch({ type: "toggle_tarea", index })}> 65 {tarea.completada ? "Desmarcar" : "Completar"} 66 </button> 67 <button onClick={() => dispatch({ type: "eliminar_tarea", index })}> 68 Eliminar 69 </button> 70 </li> 71 ))} 72 </ul> 73 </div> 74 ); 75} 76 77export default ListaTareas;
Explicación #
estadoInicial: Define una lista vacía de tareas.reducer:agregar_tarea: Añade una nueva tarea.eliminar_tarea: Elimina una tarea por su índice.toggle_tarea: Marca o desmarca una tarea como completada.
- Formulario: Agrega nuevas tareas con un campo de entrada.
6. Cuándo Usar useReducer? #
-
Estados complejos:
- Cuando tienes múltiples estados relacionados que deben actualizarse juntos.
- Ejemplo: Formularios, listas dinámicas.
-
Acciones múltiples:
- Si un estado responde a varios tipos de acciones,
useReducerorganiza mejor el código.
- Si un estado responde a varios tipos de acciones,
-
Mejor legibilidad:
- Para aplicaciones grandes o complejas,
useReducerofrece una estructura más clara y mantenible.
- Para aplicaciones grandes o complejas,
Resumen del tema
Conceptos clave #
- Patrón Reducer: función pura
(estado, accion) => nuevoEstadoque centraliza transiciones de estado deterministas según el tipo de acción (accion.type). - Sintaxis de
useReducer:const [estado, dispatch] = useReducer(reducer, estadoInicial). - Despacho de Acciones (
dispatch): emisión de objetos{ type: 'NOMBRE_ACCION', payload: datos }para desacoplar la intención del usuario de la mutación del estado. - Cuándo Elegir
useReducersobreuseState: estados con subvalores interdependientes, máquinas de estados finitos, formularios complejos o cuando la siguiente transición depende del estado anterior complejo.
Qué debes recordar #
useReducer gestiona estados complejos y transiciones estructuradas mediante funciones reductoras puras y despacho de acciones (dispatch).