Volver

¿Que es Reducer Object en React?

Introducción

En el artículo anterior sobre useReducer, vimos cómo manejar estado complejo usando el patrón tradicional con switch. Hoy vamos a explorar una alternativa más elegante y moderna: el Reducer Object Pattern. Esta técnica te permitirá escribir reducers más limpios, legibles y fáciles de mantener.

¿Qué problema resuelve?

Cuando usamos switch en nuestros reducers, el código puede volverse verbose y repetitivo. Mira este ejemplo tradicional:

function reducer(state, action) {
  switch (action.type) {
    case 'ERROR':
      return {
        ...state,
        error: true,
        loading: false,
      };
    
    case 'CHECK':
      return {
        ...state,
        loading: true,
      };
    
    case 'SUCCESS':
      return {
        ...state,
        error: false,
        loading: false,
        data: action.payload,
      };
    
    default:
      return state;
  }
}

Este código funciona perfectamente, pero tiene algunas desventajas:

  • Mucho boilerplate con case, break o return
  • Difícil de leer cuando tienes muchas acciones
  • Propenso a errores si olvidas el return o el break

El Reducer Object Pattern

El patrón de Reducer Object consiste en usar un objeto que mapea los tipos de acción a sus transformaciones de estado correspondientes. Aquí está la magia:

const reducerObject = (state) => ({
  'ERROR': {
    ...state,
    error: true,
    loading: false,
  },

  'CHECK': {
    ...state,
    loading: true,
  },

  'SUCCESS': {
    ...state,
    error: false,
    loading: false,
    data: state.tempData,
  }
});

const reducer = (state, action) => {
  if (reducerObject(state)[action.type]) {
    return reducerObject(state)[action.type];
  } else {
    return state;
  }
};

¿Cómo funciona?

  1. reducerObject: Es una función que recibe el estado actual y retorna un objeto donde cada clave es un tipo de acción y su valor es el nuevo estado
  2. reducer: Busca si existe una transformación para el action.type en el objeto y la retorna, o devuelve el estado sin cambios si no existe

Ejemplo práctico: Sistema de autenticación

Vamos a construir un sistema de autenticación completo usando este patrón.

Paso 1: Define el estado inicial

const initialState = {
  user: null,
  isAuthenticated: false,
  loading: false,
  error: null,
  token: null
};

Paso 2: Crea el reducerObject

const authReducerObject = (state, action) => ({
  'LOGIN_START': {
    ...state,
    loading: true,
    error: null,
  },

  'LOGIN_SUCCESS': {
    ...state,
    loading: false,
    isAuthenticated: true,
    user: action.payload.user,
    token: action.payload.token,
    error: null,
  },

  'LOGIN_ERROR': {
    ...state,
    loading: false,
    isAuthenticated: false,
    user: null,
    token: null,
    error: action.payload,
  },

  'LOGOUT': {
    ...initialState,
  },

  'UPDATE_PROFILE': {
    ...state,
    user: {
      ...state.user,
      ...action.payload,
    },
  },

  'CLEAR_ERROR': {
    ...state,
    error: null,
  }
});

Paso 3: Crea el reducer

const authReducer = (state, action) => {
  const newState = authReducerObject(state, action)[action.type];
  return newState ? newState : state;
};

Paso 4: Usa el reducer en tu componente

import { useReducer } from 'react';

function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, initialState);

  const login = async (email, password) => {
    dispatch({ type: 'LOGIN_START' });
    
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });
      
      const data = await response.json();
      
      if (response.ok) {
        dispatch({ 
          type: 'LOGIN_SUCCESS', 
          payload: data 
        });
      } else {
        dispatch({ 
          type: 'LOGIN_ERROR', 
          payload: data.message 
        });
      }
    } catch (error) {
      dispatch({ 
        type: 'LOGIN_ERROR', 
        payload: 'Error de conexión' 
      });
    }
  };

  const logout = () => {
    dispatch({ type: 'LOGOUT' });
  };

  const updateProfile = (updates) => {
    dispatch({ 
      type: 'UPDATE_PROFILE', 
      payload: updates 
    });
  };

  return (
    <AuthContext.Provider value={{ state, login, logout, updateProfile }}>
      {children}
    </AuthContext.Provider>
  );
}

Versión mejorada: Con payload dinámico

Podemos mejorar nuestro patrón para que sea aún más flexible:

const taskReducerObject = (state, action) => ({
  'ADD_TASK': {
    ...state,
    tasks: [...state.tasks, {
      id: Date.now(),
      text: action.payload,
      completed: false,
      createdAt: new Date().toISOString()
    }],
  },

  'TOGGLE_TASK': {
    ...state,
    tasks: state.tasks.map(task =>
      task.id === action.payload
        ? { ...task, completed: !task.completed }
        : task
    ),
  },

  'DELETE_TASK': {
    ...state,
    tasks: state.tasks.filter(task => task.id !== action.payload),
  },

  'EDIT_TASK': {
    ...state,
    tasks: state.tasks.map(task =>
      task.id === action.payload.id
        ? { ...task, text: action.payload.text }
        : task
    ),
  },

  'SET_FILTER': {
    ...state,
    filter: action.payload, // 'all', 'active', 'completed'
  },

  'CLEAR_COMPLETED': {
    ...state,
    tasks: state.tasks.filter(task => !task.completed),
  }
});

const taskReducer = (state, action) => {
  return taskReducerObject(state, action)[action.type] || state;
};

Componente completo: To-Do List

import { useReducer } from 'react';

const initialState = {
  tasks: [],
  filter: 'all' // 'all', 'active', 'completed'
};

function TodoApp() {
  const [state, dispatch] = useReducer(taskReducer, initialState);

  const addTask = (text) => {
    if (text.trim()) {
      dispatch({ type: 'ADD_TASK', payload: text });
    }
  };

  const toggleTask = (id) => {
    dispatch({ type: 'TOGGLE_TASK', payload: id });
  };

  const deleteTask = (id) => {
    dispatch({ type: 'DELETE_TASK', payload: id });
  };

  const editTask = (id, text) => {
    dispatch({ 
      type: 'EDIT_TASK', 
      payload: { id, text } 
    });
  };

  const setFilter = (filter) => {
    dispatch({ type: 'SET_FILTER', payload: filter });
  };

  const clearCompleted = () => {
    dispatch({ type: 'CLEAR_COMPLETED' });
  };

  const getFilteredTasks = () => {
    switch (state.filter) {
      case 'active':
        return state.tasks.filter(task => !task.completed);
      case 'completed':
        return state.tasks.filter(task => task.completed);
      default:
        return state.tasks;
    }
  };

  return (
    <div className="todo-app">
      <h1>Mi Lista de Tareas</h1>
      
      <TaskInput onAdd={addTask} />
      
      <FilterButtons 
        currentFilter={state.filter}
        onFilterChange={setFilter}
      />
      
      <TaskList
        tasks={getFilteredTasks()}
        onToggle={toggleTask}
        onDelete={deleteTask}
        onEdit={editTask}
      />
      
      <div className="footer">
        <span>{state.tasks.filter(t => !t.completed).length} tareas pendientes</span>
        <button onClick={clearCompleted}>
          Limpiar completadas
        </button>
      </div>
    </div>
  );
}

Ventajas del Reducer Object Pattern

  1. Más limpio: Menos boilerplate y sintaxis más clara
  2. Más legible: Cada acción es un objeto con su transformación
  3. Fácil de extender: Agregar nuevas acciones es simplemente agregar una nueva propiedad
  4. Menos errores: No hay que preocuparse por break o return olvidados
  5. Mejor organización: Todas las transformaciones están en un solo lugar

Comparación: Switch vs Object

Con Switch (Tradicional)

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'RESET':
      return { ...state, count: 0 };
    default:
      return state;
  }
}

Con Object Pattern (Moderno)

const reducerObject = (state) => ({
  'INCREMENT': { ...state, count: state.count + 1 },
  'DECREMENT': { ...state, count: state.count - 1 },
  'RESET': { ...state, count: 0 }
});

const reducer = (state, action) => 
  reducerObject(state)[action.type] || state;

Cuándo usar cada patrón

Usa Switch cuando:

  • Necesitas lógica compleja dentro de cada caso
  • Tienes transformaciones que requieren múltiples pasos
  • El equipo está más familiarizado con este patrón

Usa Object Pattern cuando:

  • Tienes muchas acciones simples
  • Quieres código más conciso
  • Buscas mejor legibilidad
  • Las transformaciones son directas

Tips y mejores prácticas

1. Combina con constantes

const ACTIONS = {
  ADD_TASK: 'ADD_TASK',
  DELETE_TASK: 'DELETE_TASK',
  TOGGLE_TASK: 'TOGGLE_TASK'
};

const reducerObject = (state, action) => ({
  [ACTIONS.ADD_TASK]: {
    ...state,
    tasks: [...state.tasks, action.payload]
  },
  // ...
});

2. Extrae la lógica compleja

const addTaskLogic = (state, payload) => ({
  ...state,
  tasks: [...state.tasks, {
    id: Date.now(),
    text: payload,
    completed: false
  }]
});

const reducerObject = (state, action) => ({
  'ADD_TASK': addTaskLogic(state, action.payload),
  // ...
});

3. Usa default values

const reducer = (state = initialState, action) => {
  return reducerObject(state, action)[action.type] ?? state;
};

Conclusión

El Reducer Object Pattern es una alternativa elegante y moderna al patrón tradicional de switch. No reemplaza completamente el uso de switch – cada uno tiene su lugar – pero para muchos casos de uso, especialmente cuando tienes múltiples acciones simples, este patrón puede hacer tu código más limpio y mantenible.

Recuerda que lo más importante no es qué patrón uses, sino que tu código sea consistente, legible y fácil de mantener para todo el equipo. Experimenta con ambos enfoques y elige el que mejor se adapte a tu caso de uso específico.

Referencias

Avatar

Autor

Elan Francisco P. Asprilla
Desarrollador Frontend

Artículos relacionados

Banner Contact (Hidrotecno)

El componente “Banner Contacto” es...

Banner Video (Palmas)

Este banner te permite visualizar un video.

Alias de git

Hay forma de acortar los comandos de git, para...

Cómo Crear y Configurar Header y Footer con el Plugin EAU en...

Si trabajas con WordPress y Elementor,...