Volver

useReducer en React: Guía Completa

¿Qué es useReducer?

useReducer es un Hook de React que te permite manejar estados complejos en tus componentes de una manera más predecible y organizada. Es una alternativa a useState que resulta especialmente útil cuando tu estado tiene múltiples valores relacionados o cuando las actualizaciones dependen de valores anteriores.

¿Cuándo usar useReducer?

Deberías considerar useReducer cuando:

  • Tu estado tiene múltiples valores que cambian juntos
  • La lógica de actualización es compleja
  • Quieres centralizar la lógica de estado en un solo lugar
  • Necesitas optimizar el rendimiento pasando dispatch a componentes hijos

Conceptos básicos

useReducer se basa en tres conceptos fundamentales:

Estado (State): El valor actual de tus datos.

Acción (Action): Un objeto que describe qué cambio quieres hacer. Por convención, tiene una propiedad type que indica el tipo de acción.

Reducer: Una función pura que toma el estado actual y una acción, y devuelve el nuevo estado.

Sintaxis básica

const [state, dispatch] = useReducer(reducer, initialState);

El Hook retorna dos valores: el estado actual y una función dispatch para enviar acciones.

Ejemplo práctico

Vamos a crear un contador con múltiples operaciones para entender cómo funciona:

import { useReducer } from 'react';

// 1. Definir el reducer
function 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 };
    case 'set':
      return { count: action.payload };
    default:
      throw new Error(`Acción desconocida: ${action.type}`);
  }
}

// 2. Usar el reducer en un componente
function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Contador: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        +1
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>
        -1
      </button>
      <button onClick={() => dispatch({ type: 'reset' })}>
        Reset
      </button>
      <button onClick={() => dispatch({ type: 'set', payload: 10 })}>
        Establecer en 10
      </button>
    </div>
  );
}

Ejemplo más complejo: Lista de tareas

Para ver el verdadero poder de useReducer, creemos un gestor de tareas:

function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        { id: Date.now(), text: action.payload, completed: false }
      ];
    case 'toggle':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case 'delete':
      return state.filter(todo => todo.id !== action.payload);
    case 'edit':
      return state.map(todo =>
        todo.id === action.payload.id
          ? { ...todo, text: action.payload.text }
          : todo
      );
    default:
      return state;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [input, setInput] = useState('');

  const handleAdd = () => {
    if (input.trim()) {
      dispatch({ type: 'add', payload: input });
      setInput('');
    }
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Nueva tarea"
      />
      <button onClick={handleAdd}>Agregar</button>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => dispatch({ type: 'toggle', payload: todo.id })}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => dispatch({ type: 'delete', payload: todo.id })}>
              Eliminar
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Inicialización diferida

Cuando el estado inicial requiere cálculos costosos, puedes usar una función inicializadora:

function init(initialCount) {
  return { count: initialCount };
}

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  // ...
}

useReducer vs useState

Usa useState cuando:

  • El estado es simple (un valor primitivo)
  • No hay lógica compleja de actualización
  • Los valores del estado son independientes

Usa useReducer cuando:

  • El estado tiene múltiples valores relacionados
  • Las actualizaciones son complejas
  • Quieres testear la lógica de estado separadamente
  • Necesitas optimizar renders de componentes hijos

Buenas prácticas

Mantén los reducers puros: no modifiques el estado directamente, siempre retorna un nuevo objeto. Define tipos de acciones como constantes para evitar errores tipográficos. Usa TypeScript para tipar acciones y estado, mejorando la seguridad. Considera combinar useReducer con useContext para gestión de estado global. Maneja casos default en tus reducers para detectar errores.

Conclusión

useReducer es una herramienta poderosa para manejar estado complejo en React. Aunque tiene una curva de aprendizaje inicial, te ayudará a escribir código más mantenible y predecible cuando tu aplicación crece en complejidad.

Avatar

Autor

Elan Francisco P. Asprilla
Desarrollador Frontend

Artículos relacionados

¿Cómo activar Layout builder?

Para activar la opción para agregar los campos...

Banner Contact (Hidrotecno)

El componente “Banner Contacto” es...

¿Cómo configurar la página que se ve en la URL Raíz?

Para configurar qué página se verá en el...

Plugin personalizado post en archivo excel WP (Hidrotecno)

Este artículo es una continuación Formulario de...