Como Usar Zustand no React: Guia Prático de Gerenciamento de Estado
Aprenda como usar o Zustand para gerenciar estados globais no React de forma simples, leve e performática. Crie um carrinho de compras com TypeScript do zero, sem boilerplate.
Gerenciar estado global no React sempre foi um tema de debate intenso. Por muito tempo, desenvolvedores ficaram divididos entre a complexidade e o boilerplate do Redux e a simplicidade (mas com problemas de performance) da Context API. É nesse cenário que o Zustand se consolidou como uma das soluções mais elegantes e eficientes do ecossistema frontend.
Neste guia prático, você vai aprender como usar zustand para criar stores globais robustas, tipadas com TypeScript, persistidas automaticamente no localStorage e otimizadas para evitar re-renderizações desnecessárias. Tudo isso sem precisar envelopar sua aplicação em dezenas de Context Providers.
O que é o Zustand e por que ele se destaca no React?
O Zustand é uma biblioteca de gerenciamento de estado minimalista desenvolvida pela Poimandres (mesma criadora do react-spring e react-three-fiber). Ele foi desenhado para resolver os principais problemas das soluções tradicionais de forma extremamente simples.
Os principais pontos que fazem o Zustand se destacar são:
- Tamanho de bundle minúsculo: Com apenas ~1.1 kB gzipped, de acordo com dados do Bundlephobia, ele praticamente não impacta o tempo de carregamento da sua aplicação.
- Sem Context Providers: Você não precisa envolver a árvore de componentes em um
<MyStoreProvider>. O estado é armazenado em uma store externa e os componentes se inscrevem diretamente nela por meio de hooks. - Otimização nativa: O Zustand utiliza um sistema baseado em seletores para garantir que um componente só seja renderizado novamente se a fatia de estado que ele consome realmente mudar.
- API simples e opinativa: Diferente do Redux, você não precisa configurar reducers, actions, dispatchers e types separados para realizar uma alteração simples de estado.
Para conhecer mais detalhes técnicos e acompanhar o desenvolvimento da biblioteca, você pode acessar o repositório oficial do Zustand no GitHub.
Zustand vs. Context API vs. Redux Toolkit: Quando escolher cada um?
A escolha da ferramenta de estado ideal depende diretamente dos requisitos do seu projeto e da complexidade da arquitetura. A tabela abaixo resume os principais trade-offs:
| Critério | Context API | Redux Toolkit | Zustand |
|---|---|---|---|
| Boilerplate | Baixo | Alto | Baixíssimo |
| Curva de Aprendizado | Baixa | Alta | Baixa |
| Performance (Re-renders) | Ruim (sem otimização manual) | Excelente | Excelente |
| Tamanho do Bundle | Nativo (0 kB) | ~11 kB (gzipped) | ~1.1 kB (gzipped) |
| Ideal para | Estados estáticos ou de baixa frequência (ex: tema, idioma) | Aplicações corporativas massivas com fluxos complexos de middleware | Projetos de pequeno a grande porte que exigem agilidade e performance |
| Necessita de Providers | Sim | Sim | Não |
A Context API é excelente para dados que mudam raramente. No entanto, quando usada para estados de alta frequência (como formulários ou carrinhos de compras), ela força a re-renderização de todos os componentes consumidores sempre que qualquer propriedade do contexto é atualizada.
O Redux Toolkit continua sendo uma ferramenta poderosa para ecossistemas complexos e legados estruturados. Contudo, para a maioria das aplicações modernas, o Zustand oferece a mesma performance de renderização com uma fração do código escrito. Essa flexibilidade de ferramentas é um dos grandes motivos de debate quando comparamos ecossistemas, como discutido na análise entre Angular vs React.
Criando e tipando sua primeira Store com TypeScript
Antes de configurarmos nossa store, certifique-se de que você já sabe como criar um projeto em React estruturado com TypeScript. Com o projeto pronto, instale o Zustand executando o comando abaixo no seu terminal:
npm install zustand
yarn add zustand
Para garantir a segurança de tipos e o preenchimento automático no seu editor de código, vamos definir a estrutura do nosso estado e das nossas ações usando TypeScript.
No exemplo abaixo, criaremos uma store simples para gerenciar o estado de autenticação de um usuário:
import { create } from 'zustand';
// 1. Definição do tipo do usuário
interface User {
id: string;
name: string;
email: string;
}
// 2. Definição do estado e das ações da Store
interface AuthState {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
// 3. Criação da store tipada
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
Note que a função create recebe um callback com o argumento set. Esse método é usado para atualizar o estado de forma imutável.
Guia Prático: Implementando um Carrinho de Compras do Zero
Vamos construir um exemplo clássico e altamente prático: um carrinho de compras. Ele precisa permitir a adição de produtos, remoção de itens e a limpeza completa do carrinho.
Crie um arquivo chamado useCartStore.ts:
import { create } from 'zustand';
export interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartState {
cart: CartItem[];
addItem: (product: Omit<CartItem, 'quantity'>) => void;
removeItem: (productId: number) => void;
clearCart: () => void;
}
export const useCartStore = create<CartState>((set) => ({
cart: [],
addItem: (product) =>
set((state) => {
const existingItem = state.cart.find((item) => item.id === product.id);
if (existingItem) {
// Se o item já existe, incrementa a quantidade
return {
cart: state.cart.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
};
}
// Se é um item novo, adiciona ao array usando o operador spread
return { cart: [...state.cart, { ...product, quantity: 1 }] };
}),
removeItem: (productId) =>
set((state) => ({
cart: state.cart
.map((item) =>
item.id === productId ? { ...item, quantity: item.quantity - 1 } : item
)
.filter((item) => item.quantity > 0), // Remove do carrinho se a quantidade chegar a zero
})),
clearCart: () => set({ cart: [] }),
}));
No código acima, utilizamos o operador spread para garantir a imutabilidade do estado ao adicionar novos itens ao array cart. Essa prática evita efeitos colaterais comuns no ciclo de vida do React.
Otimização de Performance: Evitando re-renderizações com Seletores
Um dos erros mais comuns ao utilizar o Zustand é importar a store inteira dentro de um componente quando apenas uma informação específica é necessária.
Se você fizer isso:
// EVITE: Isso fará o componente re-renderizar sempre que QUALQUER parte da store mudar
const state = useCartStore();
Para otimizar a performance, utilize seletores (selectors). Eles instruem o Zustand a observar apenas a propriedade selecionada. Se outras propriedades da store mudarem, o componente não sofrerá re-renderização.
Veja um exemplo de componente de Header que precisa apenas exibir a quantidade total de itens no carrinho:
import React from 'react';
import { useCartStore } from './useCartStore';
export function Header() {
// O componente só re-renderiza se o valor retornado por este seletor mudar
const totalItems = useCartStore((state) =>
state.cart.reduce((acc, item) => acc + item.quantity, 0)
);
return (
<header style={{ padding: '1rem', borderBottom: '1px solid #ccc' }}>
<h2>Minha Loja</h2>
<div>🛒 Carrinho: <strong>{totalItems} itens</strong></div>
</header>
);
}
Se um usuário alterar o nome de um produto ou adicionar um cupom de desconto na store, o componente Header continuará intacto, pois o resultado numérico do seletor de quantidade não foi alterado.
Persistência de Estado e Acesso fora do Contexto do React
Persistindo dados no localStorage de forma automática
O Zustand fornece middlewares nativos extremamente úteis. O middleware persist permite salvar o estado da store automaticamente no localStorage (ou sessionStorage), hidratando o estado da aplicação assim que ela é recarregada.
Vamos atualizar nossa store de carrinho para incluir a persistência automática:
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
// ... interfaces CartItem e CartState permanecem iguais
export const useCartStore = create<CartState>()(
persist(
(set) => ({
cart: [],
addItem: (product) => set((state) => { /* lógica */ return { cart: [] } }),
removeItem: (productId) => set((state) => { /* lógica */ return { cart: [] } }),
clearCart: () => set({ cart: [] }),
}),
{
name: 'shopping-cart-storage', // chave única para o localStorage
storage: createJSONStorage(() => localStorage), // define o tipo de storage
}
)
);
Acessando e atualizando o estado fora de componentes React
Uma das maiores vantagens do Zustand sobre a Context API é a capacidade de ler ou atualizar valores da store fora de componentes React (em arquivos JS/TS puros). Isso é extremamente útil para integrar com interceptors do Axios ou utilitários de log.
Imagine que você precisa injetar um token de autenticação salvo na store do Zustand em todas as requisições HTTP da sua API:
import axios from 'axios';
import { useAuthStore } from './useAuthStore';
const api = axios.create({
baseURL: 'https://api.exemplo.com',
});
api.interceptors.request.use((config) => {
// Acessa o estado atual de forma imperativa, sem hooks
const token = useAuthStore.getState().user?.id;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Você também pode atualizar o estado de qualquer lugar usando useAuthStore.setState({ user: null, isAuthenticated: false }).
Depuração Simplificada com Redux DevTools
Se você sente falta da visualização do fluxo de estados que o Redux proporciona, o Zustand possui integração nativa com a extensão Redux DevTools através do middleware devtools.
Para ativar, basta envelopar sua store com o middleware:
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface CounterState {
count: number;
increment: () => void;
}
export const useCounterStore = create<CounterState>()(
devtools(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }), false, 'increment'),
}),
{ name: 'CounterStore' }
)
);
Agora, ao abrir o Redux DevTools no seu navegador, você conseguirá inspecionar cada alteração de estado, realizar time-travel debugging e monitorar a performance da aplicação de forma visual.
Referências
Perguntas Frequentes (FAQ)
O Zustand substitui o Redux em 100% dos cenários?
Não. Embora o Zustand seja excelente para a grande maioria das aplicações modernas devido à sua simplicidade e performance, o Redux Toolkit ainda pode ser preferível em sistemas legados complexos, arquiteturas de microsserviços estritas ou quando a equipe já possui fluxos de trabalho altamente consolidados com middlewares específicos do Redux.
É preciso usar um Context Provider para o Zustand funcionar?
Não. Diferente da Context API do React, o Zustand armazena o estado em uma store externa e utiliza um sistema de subscrição baseado em hooks. Isso permite que qualquer componente acesse o estado diretamente, sem a necessidade de envolver a aplicação em Providers.
Como o Zustand evita re-renderizações desnecessárias?
O Zustand utiliza seletores (selectors). Quando você define exatamente qual fatia do estado um componente precisa (ex: state => state.totalItems), o componente só será renderizado novamente se aquele valor específico sofrer alteração, ignorando mudanças em outras partes da store.
O Zustand resolve problemas de concorrência de estado no React 18?
O Zustand é compatível com o React 18, mas ele não resolve problemas de concorrência de estado de forma mágica. Para cenários que exigem renderização concorrente e transições de estado complexas, o desenvolvedor ainda deve utilizar recursos nativos como o useTransition do React.
Sobre Marcos Costa
Desenvolvedor backend com foco em arquitetura de software, automação e produtos digitais.
Ver mais artigos

