Curso react nivel medio

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.


¿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.

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

  1. estadoInicial: Define el estado inicial del contador.
  2. reducer: Maneja las acciones (incrementar, decrementar, reiniciar) y devuelve el nuevo estado.
  3. dispatch: Envía una acción al reducer para actualizar el estado.

Ventajas de useReducer Sobre useState

  1. Estructura: Maneja estados complejos de manera más predecible.
  2. Escalabilidad: Ideal para lógica de estado complicada o relacionada.
  3. Agrupación de Actualizaciones: Permite combinar varias acciones en un solo reducer.

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

  1. estadoInicial: Contiene los valores iniciales del formulario.
  2. reducer:
    • actualizar_campo: Actualiza el valor de un campo específico.
    • validar: Realiza validaciones y actualiza los errores.
  3. Errores: Se muestran mensajes si no se cumplen las condiciones de validación.

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

  1. estadoInicial: Define una lista vacía de tareas.
  2. 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.
  3. Formulario: Agrega nuevas tareas con un campo de entrada.

¿Cuándo Usar useReducer?

  1. Estados complejos:

    • Cuando tienes múltiples estados relacionados que deben actualizarse juntos.
    • Ejemplo: Formularios, listas dinámicas.
  2. Acciones múltiples:

    • Si un estado responde a varios tipos de acciones, useReducer organiza mejor el código.
  3. Mejor legibilidad:

    • Para aplicaciones grandes o complejas, useReducer ofrece una estructura más clara y mantenible.

Conclusión

useReducer es una alternativa poderosa a useState, especialmente en casos de lógica de estado compleja. Ayuda a estructurar y organizar tu lógica, haciendo que el código sea más predecible y escalable.