Volver al blog

State Management Moderno en 2025: Por Qué Zustand y Jotai Están Superando a Redux

Hola HaWkers, durante años Redux fue el estándar de oro para gestión de estado en React. Pero en 2025, desarrolladores están abandonando Redux en masa por soluciones más simples y modernas.

Zustand y Jotai representan una nueva generación de state management: menos boilerplate, mejor performance, y developer experience superior.

El Problema con Redux

// Redux - Mucho boilerplate para algo simple
// actions/counter.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

// reducers/counter.js
const initialState = { count: 0 };

export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

// store.js
import { createStore, combineReducers } from 'redux';
import counterReducer from './reducers/counter';

const rootReducer = combineReducers({
  counter: counterReducer
});

export const store = createStore(rootReducer);

// Component.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions/counter';

function Counter() {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

// ¡6 archivos, ~60 líneas para un contador! 😱

Zustand: Simplicidad Extrema

// store/counter.js - ¡TODO el state management en un archivo!
import { create } from 'zustand';

export const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}));

// Component.jsx
import { useCounterStore } from './store/counter';

function Counter() {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// 2 archivos, ~20 líneas. ¡Mucho más simple! ✨

Zustand Features Avanzadas

// store/user.js - Store completa con middleware
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

export const useUserStore = create(
  devtools(
    persist(
      (set, get) => ({
        user: null,
        token: null,
        isAuthenticated: false,

        // Actions
        login: async (email, password) => {
          const response = await fetch('/api/login', {
            method: 'POST',
            body: JSON.stringify({ email, password })
          });

          const { user, token } = await response.json();

          set({
            user,
            token,
            isAuthenticated: true
          });
        },

        logout: () => set({
          user: null,
          token: null,
          isAuthenticated: false
        }),

        updateProfile: (updates) => set((state) => ({
          user: { ...state.user, ...updates }
        })),

        // Selectors computados
        get fullName() {
          const { user } = get();
          return user ? `${user.firstName} ${user.lastName}` : '';
        }
      }),
      {
        name: 'user-storage', // LocalStorage key
        partialize: (state) => ({
          token: state.token,
          user: state.user
        })
      }
    )
  )
);

Jotai: Atomic State Management

// atoms/counter.js
import { atom } from 'jotai';

// Atoms primitivos
export const countAtom = atom(0);

// Derived atoms (computed)
export const doubledCountAtom = atom((get) => get(countAtom) * 2);

// Write-only atom (actions)
export const incrementAtom = atom(
  null,
  (get, set) => set(countAtom, get(countAtom) + 1)
);

export const decrementAtom = atom(
  null,
  (get, set) => set(countAtom, get(countAtom) - 1)
);

// Component.jsx
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { countAtom, doubledCountAtom, incrementAtom } from './atoms/counter';

function Counter() {
  const count = useAtomValue(countAtom);
  const doubled = useAtomValue(doubledCountAtom);
  const increment = useSetAtom(incrementAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubled}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

Jotai con Async Data

// atoms/users.js
import { atom } from 'jotai';

// Async atom - busca datos automáticamente
export const usersAtom = atom(async () => {
  const response = await fetch('/api/users');
  return response.json();
});

// Atom con refresh
export const refreshUsersAtom = atom(null, async (get, set) => {
  const users = await fetch('/api/users').then(r => r.json());
  set(usersAtom, users);
});

// Filtered atom
export const activeUsersAtom = atom((get) => {
  const users = get(usersAtom);
  return users.filter(user => user.active);
});

// Component.jsx - ¡Suspense out-of-the-box!
import { Suspense } from 'react';
import { useAtomValue } from 'jotai';
import { usersAtom, activeUsersAtom } from './atoms/users';

function UserList() {
  const activeUsers = useAtomValue(activeUsersAtom);

  return (
    <ul>
      {activeUsers.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// App.jsx
function App() {
  return (
    <Suspense fallback={<div>Cargando usuarios...</div>}>
      <UserList />
    </Suspense>
  );
}

Comparación: Redux vs Zustand vs Jotai

Feature Redux Zustand Jotai
Bundle Size ~40 KB ~1.2 KB ~2.8 KB
Boilerplate Alto Bajo Medio
Learning Curve Empinada Suave Medio
Performance Buena Excelente Excelente
DevTools
Persistence Middleware Built-in Extensiones
TypeScript Complejo Simple Excelente
Async Thunks/Saga Nativo Nativo

Cuándo Usar Cada Uno

Usa Zustand cuando:

✅ Quieres simplicidad máxima
✅ State global simple a moderado
✅ Necesitas persistence fácil
✅ Vienes de Context API
✅ Equipo pequeño/medio

Usa Jotai cuando:

✅ Prefieres atomic state
✅ Necesitas derived state complejo
✅ Suspense y async son importantes
✅ Fine-grained reactivity
✅ Influencia de Recoil/atoms

Usa Redux cuando:

✅ Proyecto enterprise grande
✅ Time-travel debugging esencial
✅ Ecosistema Redux necesario
✅ Team ya domina Redux
✅ State extremamente complejo

Migrando de Redux

// Antes: Redux
const mapStateToProps = (state) => ({
  count: state.counter.count,
  user: state.user.data
});

const mapDispatchToProps = {
  increment,
  fetchUser
};

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

// Después: Zustand
import { useCounterStore } from './store/counter';
import { useUserStore } from './store/user';

function MyComponent() {
  const count = useCounterStore(state => state.count);
  const increment = useCounterStore(state => state.increment);

  const user = useUserStore(state => state.user);
  const fetchUser = useUserStore(state => state.fetchUser);

  // ¡Mucho más simple y directo!
}

Si quieres entender mejor las tendencias modernas de React, confiere: Svelte 5 Runes: La Revolución de Reactividad donde exploramos cómo frameworks modernos están reimaginando state management.

¡Vamos a por ello! 🦅

Domina JavaScript de Verdad

El conocimiento que adquiriste en este artículo es solo el comienzo. Hay técnicas, patrones y prácticas que transforman desarrolladores principiantes en profesionales requisitados.

Formas de pago:

  • $9.90 USD (pago único)

Ver Contenido Completo

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios