react-hook-usereducer

Cómo usar el Hook useReducer en React

  • 4 min

Hemos visto que useState es el mecanismo fundamental para la reactividad en React, ideal para valores independientes y sencillos.

Sin embargo, cuando gestionamos estados interdependientes o transiciones complejas donde una sola acción debe mutar múltiples variables simultáneamente, el código tiende a volverse difícil de mantener.

Para escenarios de lógica de estado compleja, React proporciona el hook useReducer.

Por ejemplo, imagina un reproductor de música. Al darle a “Siguiente canción” tienen que pasar muchas cosas a la vez:

  1. Incrementar el índice de la pista.
  2. Reiniciar el tiempo de reproducción a 0.
  3. Establecer el estado de reproducción a true.
  4. Actualizar los metadatos de la interfaz.

Si hacemos esto con useState, tendremos cuatro llamadas a set... dispersas por el código. Si hay mas de un sitio donde hacer play, además, lo tendríamos todo duplicado o triplicado. Aqui es donde encaja useReducer.

¿Cuándo usar cuál?

useState: Para valores independientes, primitivos (números, booleanos) o formularios simples. Es lo que usaréis el 90% de las veces

useReducer: Cuando el siguiente estado depende mucho del anterior, o tenéis logica compleja (múitples variables, objetos anidados, etc)

Sintaxis de useReducer

El hook useReducer es una función de React que permite gestionar el estado de un componente utilizando un patrón de reducer.

Este patrón es especialmente útil para manejar estados complejos o cuando el estado depende de acciones previas.

La sintaxis del hook useReducer es la siguiente,

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

Como vemos recibe dos argumentos,

  1. Reducer: Una función que toma el estado actual y una acción, y retorna un nuevo estado.
  2. Estado inicial: El valor inicial del estado.

Por otro lado, devuelve un array con dos elementos,

  1. Estado actual: El valor actual del estado.
  2. Dispatch: Una función que permite enviar acciones al reducer para actualizar el estado.

Estructura de un Reducer

Hemos dicho todo el rato que vamos a trabajar con el patrón Reducer. ¿Pero qué es un reducer? Es una función pura de JavaScript, que

  • Recibe el estado actual y una acción
  • Devuelve el nuevo estado.

Es decir, una sintaxis así,

function reducer(state, action) {
  switch (action.type) {
    case 'ACCION_1':
      // Retorna un nuevo estado basado en ACCION_1
      return nuevoEstado;
    case 'ACCION_2':
      // Retorna un nuevo estado basado en ACCION_2
      return nuevoEstado;
    default:
      // Retorna el estado actual si la acción no es reconocida
      return state;
  }
}
Copied!

Por convención, las Acciones son objetos con dos propiedades:

// Acción estándar
{
  type: 'ACTUALIZAR_EMAIL',
  payload: '[email protected]'
}
Copied!
  • type: Un string en mayúsculas que describe qué ha pasado (INCREMENT, DELETE_USER, FETCH_START).
  • payload (opcional): Los datos necesarios para realizar la acción (el ID a borrar, el objeto usuario a guardar, etc).

Ejemplo

¿Bastante locura de términos? No te preocupes, es normal. Vamos a intentar dejarlo más claro con un ejemplo.

Es una función pura de JavaScript. Recibe el estado actual y una acción, y devuelve el nuevo estado.

// reducer.js
function tareaReducer(state, action) {
  switch (action.type) {
    case 'AGREGAR':
      return [...state, { id: Date.now(), text: action.payload }];
    case 'BORRAR':
      return state.filter(tarea => tarea.id !== action.payload);
    case 'LIMPIAR':
      return [];
    default:
      return state;
  }
}

Copied!
import { useReducer } from 'react';

export default function ListaTareas() {
  // Inicializamos el hook
  const [tareas, dispatch] = useReducer(tareaReducer, []);

  const handleAdd = () => {
    // ENVIAMOS UNA ACCIÓN (No modificamos el estado directamente)
    dispatch({ 
      type: 'AGREGAR', 
      payload: 'Nueva tarea' 
    });
  };

  return (
    <div>
      <button onClick={handleAdd}>Agregar Tarea</button>
      
      <ul>
        {tareas.map(t => (
          <li key={t.id}>
            {t.text}
            <button onClick={() => dispatch({ type: 'BORRAR', payload: t.id })}>
              X
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Copied!

¿Por qué es mejor esto que useState?

A primera vista parece mucho más código para hacer lo mismo. Y es cierto: para un contador simple, useReducer es matar moscas a cañonazos.

Desacoplamiento

La lógica de cómo se actualiza el estado está fuera del componente. Podríais mover la función tareaReducer a otro archivo y testearla de forma aislada sin renderizar React.

Intención clara

Al leer dispatch({ type: 'LOGIN_SUCCESS' }), entendéis exactamente qué ha pasado en la aplicación. Al leer setIsAuthenticated(true); setUser(data); setLoading(false);, tenéis que descifrar qué significa ese conjunto de cambios.

Depuración

Si el estado es incorrecto, sabéis que el error está en el reducer. Con useState, el error podría estar en cualquiera de las 20 funciones que llaman a setState.