Guía Completa para Dominar el Hook useReducer en React

useReducer es un hook de React que te permite manejar estados complejos en componentes funcionales, proporcionando una alternativa al uso de useState. Es particularmente útil cuando tienes múltiples actualizaciones de estado o lógica más elaborada.


¿Qué es el Hook useReducer?

Es un hook que implementa el patrón de los reducers, similar al usado en Redux. Se basa en una función reducer que toma el estado actual y una acción como argumentos, y retorna un nuevo estado.

Sintaxis Básica

jsx
const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: Función pura que actualiza el estado.
  • initialState: Valor inicial del estado.
  • state: Estado actual.
  • dispatch(action): Función que ejecuta una acción para actualizar el estado.

Ventajas de useReducer

  Ventajas de useReducer

  1. Organización: Centraliza la lógica de actualización de estado en un solo lugar.
  2. Escalabilidad: Ideal para estados con múltiples transiciones o estructuras complejas.
  3. Simplicidad: Facilita la lectura y mantenimiento del código al separar lógica y presentación.


Ejemplo 1: Contador Básico

Un contador con acciones para incrementar, decrementar y reiniciar.

jsx
import { useReducer } from "react";

const counterReducer = (state, action) => {
switch (action.type) {
  case "increment":
    return { count: state.count + 1 };
  case "decrement":
    return { count: state.count - 1 };
  case "reset":
    return { count: 0 };
  default:
    throw new Error("Acción no soportada");
}
};

const Counter = () => {
const initialState = { count: 0 };
const [state, dispatch] = useReducer(counterReducer, initialState);

return (
  <div>
    <h1>Contador: {state.count}</h1>
    <button onClick={() => dispatch({ type: "increment" })}>Incrementar</button>
    <button onClick={() => dispatch({ type: "decrement" })}>Decrementar</button>
    <button onClick={() => dispatch({ type: "reset" })}>Reiniciar</button>
  </div>
);
};

export default Counter;
  Explicación

  1. Reducer: Maneja las transiciones del estado según el tipo de acción.
  2. Estado Inicial: Un objeto con la propiedad count inicializada en 0.
  3. Dispatch: Actualiza el estado llamando al reducer con una acción.


Ejemplo 2: Manejo de Formularios

Controla el estado de múltiples campos en un formulario.

jsx
import { useReducer } from "react";

const formReducer = (state, action) => {
switch (action.type) {
  case "update_field":
    return { ...state, [action.field]: action.value };
  case "reset":
    return { name: "", email: "" };
  default:
    throw new Error("Acción no soportada");
}
};

const Form = () => {
const initialState = { name: "", email: "" };
const [state, dispatch] = useReducer(formReducer, initialState);

const handleChange = (e) => {
  const { name, value } = e.target;
  dispatch({ type: "update_field", field: name, value });
};

const handleReset = () => {
  dispatch({ type: "reset" });
};

return (
  <div>
    <h1>Formulario</h1>
    <input
      type="text"
      name="name"
      placeholder="Nombre"
      value={state.name}
      onChange={handleChange}
    />
    <input
      type="email"
      name="email"
      placeholder="Correo"
      value={state.email}
      onChange={handleChange}
    />
    <button onClick={handleReset}>Reiniciar</button>
    <p>Nombre: {state.name}</p>
    <p>Correo: {state.email}</p>
  </div>
);
};

export default Form;
  Explicación

  1. Reducer: Actualiza campos individuales o reinicia todo el formulario.
  2. Estado Inicial: Contiene los valores de los campos.
  3. handleChange: Llama a dispatch para actualizar un campo específico.

Ejemplo 3: Carrito de Compras

Maneja la lógica de agregar,eliminar y vaciar un carrito.

jsx
import React, { useReducer } from "react";

const cartReducer = (state, action) => {
switch (action.type) {
  case "add_item":
    return [...state, action.item];
  case "remove_item":
    return state.filter((item) => item.id !== action.id);
  case "clear_cart":
    return [];
  default:
    throw new Error("Acción no soportada");
}
};

const ShoppingCart = () => {
const initialState = [];
const [cart, dispatch] = useReducer(cartReducer, initialState);

const addItem = (item) => {
  dispatch({ type: "add_item", item });
};

const removeItem = (id) => {
  dispatch({ type: "remove_item", id });
};

const clearCart = () => {
  dispatch({ type: "clear_cart" });
};

return (
  <div>
    <h1>Carrito de Compras</h1>
    <button onClick={() => addItem({ id: 1, name: "Producto A" })}>
      Agregar Producto A
    </button>
    <button onClick={() => addItem({ id: 2, name: "Producto B" })}>
      Agregar Producto B
    </button>
    <button onClick={clearCart}>Vaciar Carrito</button>
    <ul>
      {cart.map((item) => (
        <li key={item.id}>
          {item.name} <button onClick={() => removeItem(item.id)}>Eliminar</button>
        </li>
      ))}
    </ul>
  </div>
);
};

export default ShoppingCart;
  Explicación

  1. Reducer: Maneja acciones de agregar, eliminar y vaciar el carrito.
  2. Estado Inicial: Es un arreglo vacío.
  3. addItem/removeItem: Llaman a dispatch con las acciones correspondientes.

Ejemplo 4: Todo List con useReducer

Este ejemplo muestra un caso más avanzado de useReducer, manejando un array de tareas (todos) junto con estados adicionales como loading y error. Es ideal para entender cómo trabajar con múltiples acciones y estados más complejos, incluyendo escenarios asincrónicos y actualizaciones condicionales. Además, este ejemplo refleja cómo mantener la lógica organizada y reutilizable en aplicaciones reales.

jsx
const todoInitialState = {
todos: [],
loading: false,
error: null
};

function todoReducer(state, action) {
switch (action.type) {
  case 'ADD_TODO':
    return {
      ...state,
      todos: [...state.todos, action.payload]
    };
  case 'REMOVE_TODO':
    return {
      ...state,
      todos: state.todos.filter(todo => todo.id !== action.payload)
    };
  case 'TOGGLE_TODO':
    return {
      ...state,
      todos: state.todos.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    };
  case 'SET_LOADING':
    return {
      ...state,
      loading: action.payload
    };
  case 'SET_ERROR':
    return {
      ...state,
      error: action.payload
    };
  default:
    return state;
}
}

function TodoList() {
const [state, dispatch] = useReducer(todoReducer, todoInitialState);

const addTodo = (text) => {
  dispatch({
    type: 'ADD_TODO',
    payload: { id: Date.now(), text, completed: false }
  });
};

return (
  <div>
    {state.loading && <p>Loading...</p>}
    {state.error && <p>Error: {state.error}</p>}
    <ul>
      {state.todos.map(todo => (
        <li
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          {todo.text}
          <button
            onClick={() => dispatch({
              type: 'TOGGLE_TODO',
              payload: todo.id
            })}
          >
            Toggle
          </button>
          <button
            onClick={() => dispatch({
              type: 'REMOVE_TODO',
              payload: todo.id
            })}
          >
            Delete
          </button>
        </li>
      ))}
    </ul>
  </div>
);
}
  Conclusión

useReducer es una herramienta poderosa para manejar estados complejos en React. Dominarlo te permitirá escribir código más limpio, organizado y fácil de escalar.

Si aún tienes dudas te recomiendo mirar estos otros ejemplos prácticos: