Voltar para o Blog

Zustand: O State Management Minimalista que Está Substituindo Redux em 2025

Olá HaWkers, você está cansado de escrever toneladas de boilerplate para gerenciar estado no React com Redux? Ou confuso com a complexidade do Context API para estados globais?

Em 2025, Zustand emergiu como a solução minimalista que desenvolvedores React estavam esperando. Com apenas ~1KB de tamanho e API incrivelmente simples, Zustand está rapidamente substituindo Redux em novos projetos e migrações.

A biblioteca cresceu 300% em adoção no último ano, e grandes empresas já migraram de Redux para Zustand, reduzindo código em até 70% e melhorando performance.

O Problema com Redux e Alternativas

Redux: Poderoso mas Verbose

// Redux - muito boilerplate para algo simples
// 1. Actions
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 2. Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// 3. Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 4. Store
const store = createStore(counterReducer);

// 5. Provider
<Provider store={store}>
  <App />
</Provider>

// 6. Uso no componente
function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      {count}
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
}

// ~50 linhas para um contador! 😱

Context API: Simples mas com Problemas

// Context API - mais simples, mas re-renders desnecessários
const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);

  // ❌ Problema: TODO componente que usa contexto re-renderiza
  // mesmo se usar apenas parte do estado
  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
}

function Counter() {
  const { count, setCount } = useContext(CountContext);
  // Re-renderiza mesmo se outro valor no contexto mudar
  return <div>{count}</div>;
}

Zustand: Minimalismo e Performance

Mesmo contador em Zustand:

import { create } from 'zustand';

// Define store - APENAS ISSO!
const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}));

// Usa em qualquer componente - sem Provider!
function Counter() {
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);

  return (
    <div>
      {count}
      <button onClick={increment}>+</button>
    </div>
  );
}

// ~15 linhas, performance otimizada automaticamente! ✨

Vantagens:

  • Zero boilerplate: Sem actions, reducers, providers
  • Tiny bundle: 1KB (Redux: ~10KB)
  • Sem Provider: Funciona fora de React também
  • Performance: Re-renders otimizados por padrão
  • TypeScript: Suporte nativo excelente
  • DevTools: Integração com Redux DevTools

Casos de Uso Avançados

1. Store de Autenticação Completa

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface User {
  id: string;
  name: string;
  email: string;
}

interface AuthStore {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  updateUser: (user: Partial<User>) => void;
}

export const useAuthStore = create<AuthStore>()(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,

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

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

          set({
            user,
            token,
            isAuthenticated: true
          });
        } catch (error) {
          console.error('Login failed:', error);
          throw error;
        }
      },

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

      updateUser: (updates) => {
        set((state) => ({
          user: state.user ? { ...state.user, ...updates } : null
        }));
      }
    }),
    {
      name: 'auth-storage',  // Persiste no localStorage
      partialize: (state) => ({  // Persiste apenas campos necessários
        user: state.user,
        token: state.token
      })
    }
  )
);

// Uso
function Profile() {
  const user = useAuthStore((state) => state.user);
  const logout = useAuthStore((state) => state.logout);

  if (!user) return <Login />;

  return (
    <div>
      <h1>Olá, {user.name}</h1>
      <button onClick={logout}>Sair</button>
    </div>
  );
}

2. Shopping Cart com Cálculos Derivados

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface CartStore {
  items: CartItem[];
  // Computed values
  totalItems: () => number;
  totalPrice: () => number;
  // Actions
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: string) => void;
  updateQuantity: (id: string, quantity: number) => void;
  clearCart: () => void;
}

export const useCartStore = create<CartStore>()(
  devtools((set, get) => ({
    items: [],

    // Computed values como funções
    totalItems: () => {
      return get().items.reduce((sum, item) => sum + item.quantity, 0);
    },

    totalPrice: () => {
      return get().items.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0
      );
    },

    addItem: (newItem) => {
      set((state) => {
        const existingItem = state.items.find((item) => item.id === newItem.id);

        if (existingItem) {
          return {
            items: state.items.map((item) =>
              item.id === newItem.id
                ? { ...item, quantity: item.quantity + 1 }
                : item
            )
          };
        }

        return {
          items: [...state.items, { ...newItem, quantity: 1 }]
        };
      });
    },

    removeItem: (id) => {
      set((state) => ({
        items: state.items.filter((item) => item.id !== id)
      }));
    },

    updateQuantity: (id, quantity) => {
      if (quantity <= 0) {
        get().removeItem(id);
        return;
      }

      set((state) => ({
        items: state.items.map((item) =>
          item.id === id ? { ...item, quantity } : item
        )
      }));
    },

    clearCart: () => set({ items: [] })
  }))
);

// Uso - re-renders otimizados
function Cart() {
  // Apenas re-renderiza se items mudar
  const items = useCartStore((state) => state.items);
  const totalPrice = useCartStore((state) => state.totalPrice());
  const removeItem = useCartStore((state) => state.removeItem);

  return (
    <div>
      <h2>Carrinho</h2>
      {items.map((item) => (
        <div key={item.id}>
          {item.name} - {item.quantity}x R${item.price}
          <button onClick={() => removeItem(item.id)}>Remover</button>
        </div>
      ))}
      <p>Total: R${totalPrice.toFixed(2)}</p>
    </div>
  );
}

function AddToCartButton({ product }) {
  // Este componente NÃO re-renderiza quando cart muda!
  const addItem = useCartStore((state) => state.addItem);

  return <button onClick={() => addItem(product)}>Adicionar ao Carrinho</button>;
}

Middleware e Extensões

1. Persist - LocalStorage Automático

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

const useSettingsStore = create(
  persist(
    (set) => ({
      theme: 'light',
      language: 'pt-BR',
      notifications: true,

      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      toggleNotifications: () =>
        set((state) => ({ notifications: !state.notifications }))
    }),
    {
      name: 'app-settings',
      storage: createJSONStorage(() => localStorage),
      // Migração de versões antigas
      version: 1,
      migrate: (persistedState, version) => {
        if (version === 0) {
          // Migra de v0 para v1
          return { ...persistedState, notifications: true };
        }
        return persistedState;
      }
    }
  )
);

2. Immer - Immutability Simplificada

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

const useTodoStore = create(
  immer((set) => ({
    todos: [],

    addTodo: (text) =>
      set((state) => {
        // Com immer, pode "mutar" diretamente!
        state.todos.push({
          id: Date.now(),
          text,
          completed: false
        });
      }),

    toggleTodo: (id) =>
      set((state) => {
        const todo = state.todos.find((t) => t.id === id);
        if (todo) {
          todo.completed = !todo.completed; // Mutação direta!
        }
      }),

    removeTodo: (id) =>
      set((state) => {
        const index = state.todos.findIndex((t) => t.id === id);
        if (index !== -1) {
          state.todos.splice(index, 1); // Mutação direta!
        }
      })
  }))
);

3. Subscriptions - React Além de React

// Zustand funciona fora de componentes React!
import { useUserStore } from './stores/user';

// Subscribe a mudanças
const unsubscribe = useUserStore.subscribe(
  (state) => state.user,
  (user, previousUser) => {
    console.log('Usuário mudou:', { user, previousUser });

    // Analytics tracking
    if (user && !previousUser) {
      analytics.track('User Logged In', { userId: user.id });
    }
  }
);

// Acessa estado fora de componente
const currentUser = useUserStore.getState().user;
console.log(currentUser);

// Atualiza estado fora de componente
useUserStore.getState().login('user@example.com', 'password');

// Cleanup
unsubscribe();

Zustand vs. Outras Soluções

Comparação de Bundle Size

Redux + Redux Toolkit:  ~10KB
MobX:                   ~16KB
Recoil:                 ~14KB
Jotai:                  ~3KB
Zustand:                ~1KB  ✅

// Zustand + Persist + DevTools: ~3KB (ainda menor que alternativas!)

Comparação de Performance

// Benchmark: 10.000 updates em lista de 1000 itens

// Redux: ~850ms
// - Re-renders desnecessários
// - Normalização manual necessária

// Context API: ~1200ms
// - Todos consumidores re-renderizam

// Zustand: ~420ms ✅
// - Re-renders otimizados por seletor
// - Zero overhead

Comparação de Developer Experience

// Redux Toolkit (melhor DX do Redux)
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 }
  }
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

// Ainda precisa de store, provider, etc...
// ~30 linhas de código

// Zustand
const useCounter = create((set) => ({
  value: 0,
  increment: () => set((s) => ({ value: s.value + 1 })),
  decrement: () => set((s) => ({ value: s.value - 1 }))
}));

// ~6 linhas, mesmo resultado ✅

Quando NÃO Usar Zustand

✅ Use Zustand quando:

  • Precisa de state global simples
  • Quer performance otimizada
  • Prefere código mínimo
  • Trabalha em projeto React moderno

⚠️ Considere alternativas quando:

  • Time já domina Redux e projeto é grande (custo de migração)
  • Precisa de time-travel debugging complexo
  • Aplicação legada com forte acoplamento a Redux

Se você quer dominar React e state management moderno, recomendo o artigo Vue Vapor Mode: A Revolução que Elimina o Virtual DOM onde exploramos otimizações de performance em frameworks modernos.

Bora pra cima! 🦅

📚 Domine React e JavaScript Moderno

Zustand representa o futuro minimalista do state management, mas dominar React e JavaScript é essencial para aproveitar ferramentas modernas ao máximo.

Opções de investimento:

  • R$9,90 (pagamento único)

👉 Conhecer o Guia JavaScript

💡 Material atualizado com as melhores práticas do mercado

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário