Saltar al contenido principal

Por Qué Dejé de Usar Redux en 2026 (Y Qué Uso Ahora)

Después de 4 años usando Redux en todos mis proyectos React, lo eliminé. Por qué Redux ya no tiene sentido para la mayoría de apps, y las alternativas que uso ahora con ejemplos reales.

Fran Cobos 7 min de lectura 1277 palabras

Tabla de contenidos

Mi primer proyecto React serio tenía Redux. Mi segundo también. Y el tercero. Redux era la respuesta a todo: ¿necesitas estado global? Redux. ¿Fetch de datos? Redux + Thunk. ¿Formularios? Redux Form. ¿El estado del modal? Redux.

Tenía un store/ con 47 archivos entre slices, thunks, selectors, types y tests. Para una app de gestión de tareas.

Un día me senté a contar las líneas de código:

Lógica de negocio real:          2.400 líneas
Boilerplate de Redux:            3.100 líneas
Ratio señal/ruido:               43% señal, 57% ruido

Más de la mitad del código era fontanería de Redux. Ese día empecé la migración.

Qué me hizo abandonar Redux

1. El boilerplate no ha mejorado (aunque Redux Toolkit ayuda)

Redux Toolkit redujo mucho el boilerplate comparado con Redux vanilla. Pero sigue siendo demasiado:

// Para hacer un CRUD de tareas necesitas:

// 1. El slice (taskSlice.ts)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchTasks = createAsyncThunk(
  'tasks/fetchAll',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/tasks');
      return response.json();
    } catch (err) {
      return rejectWithValue(err.message);
    }
  }
);

const taskSlice = createSlice({
  name: 'tasks',
  initialState: { items: [], loading: false, error: null },
  reducers: {
    clearError: (state) => { state.error = null; },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTasks.pending, (state) => { state.loading = true; })
      .addCase(fetchTasks.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTasks.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

// 2. El store (store.ts)
// 3. Los selectors (taskSelectors.ts)
// 4. El Provider en el layout
// 5. Los types de RootState y AppDispatch
// 6. Los hooks tipados useAppSelector/useAppDispatch

~80 líneas de código para… hacer un fetch y guardar el resultado.

2. El 90% del “estado global” no necesita ser global

Hice una auditoría de mi store de Redux:

Dato en el store¿Realmente necesita ser global?
Lista de tareasNo — solo la usa una página
Usuario logueadoSí — se usa en muchos sitios
Estado de un modalNo — es local del componente
Datos de un formularioNo — es local del formulario
Filtros de búsquedaNo — pertenece a la URL
Tema dark/lightSí — se usa globalmente
NotificacionesQuizás — un Context bastaría

De 12 slices en mi store, solo 2 necesitaban ser realmente globales. El resto era estado local o estado del servidor que debería venir de una caché de queries.

3. React Server Components cambiaron las reglas

Con Next.js App Router y React Server Components, muchos datos ya no pasan por el cliente:

// Antes (con Redux): fetch en el cliente → store → componente
// Ahora (con RSC): fetch en el servidor → componente directamente

// Este componente NO necesita Redux. Los datos llegan del servidor.
export default async function TaskList() {
  const tasks = await db.task.findMany({ 
    where: { userId: session.user.id } 
  });

  return (
    <ul>
      {tasks.map(task => <TaskItem key={task.id} task={task} />)}
    </ul>
  );
}

No hay loading state. No hay error state. No hay cache invalidation. El servidor hizo el fetch, renderizó el HTML, y el cliente lo recibe listo.

Qué uso ahora (y por qué)

Para datos del servidor: TanStack Query

// Antes: 80 líneas de Redux para un fetch
// Ahora: 5 líneas con TanStack Query

function TaskList() {
  const { data: tasks, isLoading, error } = useQuery({
    queryKey: ['tasks'],
    queryFn: () => fetch('/api/tasks').then(r => r.json()),
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <ul>{tasks.map(t => <TaskItem key={t.id} task={t} />)}</ul>;
}

TanStack Query te da gratis: caché, deduplicación, refetch automático, optimistic updates, retry, y stale-while-revalidate. Todo lo que en Redux tenías que implementar a mano.

Para estado global simple: Zustand

// Antes: slice + store + provider + selectors + hooks tipados
// Ahora: un archivo de 10 líneas

import { create } from 'zustand';

interface AppStore {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useAppStore = create<AppStore>((set) => ({
  theme: 'dark',
  toggleTheme: () => set((s) => ({ theme: s.theme === 'dark' ? 'light' : 'dark' })),
  user: null,
  setUser: (user) => set({ user }),
}));

// Uso: const theme = useAppStore(s => s.theme);

Sin Provider, sin boilerplate, con selectores automáticos, tipado perfecto. Para el 95% de necesidades de estado global, Zustand es todo lo que necesitas.

Para formularios: React Hook Form

// Antes: Redux Form (2.000 líneas de código para 5 formularios)
// Ahora: React Hook Form

const { register, handleSubmit, formState: { errors } } = useForm<TaskForm>();

return (
  <form onSubmit={handleSubmit(onSubmit)}>
    <input {...register('title', { required: 'El título es obligatorio' })} />
    {errors.title && <span>{errors.title.message}</span>}
    <button type="submit">Crear</button>
  </form>
);

Para estado en la URL: nuqs (o useSearchParams)

Los filtros de búsqueda, la paginación, los tabs activos… todo eso pertenece a la URL, no al store:

import { useQueryState } from 'nuqs';

function TaskFilters() {
  const [status, setStatus] = useQueryState('status', { defaultValue: 'all' });
  const [page, setPage] = useQueryState('page', { defaultValue: '1' });

  // URL: /tasks?status=pending&page=2
  // Compartible, bookmarkeable, persistente al refrescar
}

La migración: Cómo eliminé Redux paso a paso

No lo hice de golpe. Fui slice por slice en 2 semanas:

Semana 1: Datos del servidor → TanStack Query

npm install @tanstack/react-query
  1. Identifiqué qué slices eran “estado del servidor” (datos que vienen de una API)
  2. Creé hooks con useQuery y useMutation para cada uno
  3. Eliminé los slices, thunks y selectors correspondientes
  4. Resultado: -1.800 líneas de código, misma funcionalidad

Semana 2: Estado local + global → Zustand + estado local

  1. Los modals, dropdowns y UI state → useState local en cada componente
  2. El estado genuinamente global (tema, user, notificaciones) → un store de Zustand de 25 líneas
  3. Los filtros de URL → useSearchParams
  4. Resultado: -1.300 líneas más

Total

Antes:  3.100 líneas de Redux
Después: 180 líneas (Zustand store + custom hooks de TanStack Query)
Reducción: 94% menos código de gestión de estado

Y la app funciona igual. Mejor, de hecho, porque TanStack Query gestiona la caché mucho mejor que mi implementación manual con Redux.

¿Cuándo SÍ tiene sentido Redux?

No voy a decir “Redux nunca”. Hay casos donde sigue siendo la mejor opción:

  • Editores visuales (tipo Figma, Photoshop web): Undo/redo nativo con Redux + Immer
  • Apps offline-first: Redux Persist + middleware de sincronización
  • Estado compartido entre muchos componentes con lógica compleja: Middleware, sagas, epics
  • Equipos grandes que necesitan una arquitectura predecible y estandarizada

Si estás en alguno de estos casos, Redux Toolkit sigue siendo sólido. Para todo lo demás, hay alternativas más ligeras.

Resumen: Mi stack de estado en 2026

NecesidadSoluciónLíneas de setup
Datos de APITanStack Query5 por query
Estado globalZustand20-30 (un store)
FormulariosReact Hook Form0 (por form)
Estado de URLnuqs3 por param
Estado localuseState/useReducer1-5
Total~60 líneas

Vs las 3.100 líneas que tenía con Redux. No hay comparación.

Artículos relacionados

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.