<h2>Introdução ao Zustand e Sua Arquitetura</h2>
<p>Zustand é uma biblioteca de gerenciamento de estado para JavaScript e React que se destaca pela sua simplicidade, performance e curva de aprendizado suave. Diferentemente de Redux ou MobX, que exigem boilerplate significativo, Zustand oferece uma API minimalista sem perder em funcionalidade. Sua estratégia é baseada em hooks customizados e imutabilidade, permitindo criar stores de forma declarativa com poucas linhas de código.</p>
<p>A razão pela qual Zustand ganhou popularidade reside em sua filosofia: ser pequeno (cerca de 2KB), sem dependências externas e extremamente flexível. Você não precisa de actions, reducers ou dispatch explícitos — apenas crie uma função que retorna um objeto com estado e métodos. Isso significa que você pode começar simples e crescer em complexidade conforme necessário, introduzindo Slices, Middleware e Persist apenas quando forem relevantes para seu projeto.</p>
<h2>Criando Stores e Entendendo Slices</h2>
<h3>O Básico: Sua Primeira Store</h3>
<p>Uma store Zustand é criada através da função <code>create</code>, que recebe um callback. Esse callback espera uma função que retorna um objeto com o estado inicial e os métodos que o modificam. Vamos começar com um exemplo prático de uma store de carrinho de compras:</p>
<pre><code class="language-javascript">import create from 'zustand';
const useCartStore = create((set) => ({
items: [],
total: 0,
addItem: (item) => set((state) => ({
items: [...state.items, item],
total: state.total + item.price,
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id),
total: state.total - state.items.find(item => item.id === id)?.price || 0,
})),
clearCart: () => set({ items: [], total: 0 }),
}));</code></pre>
<p>O parâmetro <code>set</code> é uma função que recebe um novo estado (ou um callback que retorna o novo estado) e o mescla com o estado atual. Note que você é responsável por manter a imutabilidade — Zustand não força isso, mas é a prática correta.</p>
<h3>Entendendo Slices: Organização em Larga Escala</h3>
<p>Quando sua aplicação cresce, manter tudo em uma única store pode ficar desordenado. Aqui entram os <strong>Slices</strong> — padrão de organização que divide a store em pedaços lógicos. A ideia é criar funções que retornam partes do estado e seus métodos, depois combiná-las em uma única store.</p>
<p>Vamos refatorar nosso exemplo usando slices. Primeiro, criamos um slice para o carrinho:</p>
<pre><code class="language-javascript">import create from 'zustand';
// Slice: Gerenciamento do Carrinho
const createCartSlice = (set) => ({
items: [],
total: 0,
addItem: (item) => set((state) => ({
items: [...state.items, item],
total: state.total + item.price,
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id),
total: state.total - (state.items.find(item => item.id === id)?.price || 0),
})),
clearCart: () => set({ items: [], total: 0 }),
});
// Slice: Gerenciamento de Usuário
const createUserSlice = (set) => ({
user: null,
isAuthenticated: false,
setUser: (user) => set({ user, isAuthenticated: !!user }),
logout: () => set({ user: null, isAuthenticated: false }),
});
// Combinando Slices em uma Store Única
const useAppStore = create((set) => ({
...createCartSlice(set),
...createUserSlice(set),
}));</code></pre>
<p>Essa abordagem oferece várias vantagens: cada slice é isolado logicamente, testável separadamente, e fácil de manter. Quando seu projeto escalou para 50+ estados, essa organização evita que tudo se torne um caos. Você pode até mesmo colocar cada slice em um arquivo separado e importá-los conforme necessário.</p>
<h2>Middleware: Interceptando e Transformando Ações</h2>
<h3>O Que é Middleware no Contexto de Zustand</h3>
<p>Middleware no Zustand funciona de forma similar a outras bibliotecas: é uma camada que intercepta chamadas de <code>set</code> e permite transformar, logar ou validar mudanças de estado. Zustand oferece uma API simplificada para isso através de funções que envolvem a store. O middleware recebe acesso ao estado anterior, à função <code>set</code>, à ação que está sendo realizada e metadados.</p>
<h3>Implementando Seu Primeiro Middleware</h3>
<p>Vamos criar um middleware que registra todas as mudanças de estado — uma prática comum em desenvolvimento:</p>
<pre><code class="language-javascript">import create from 'zustand';
// Middleware de Logging
const loggerMiddleware = (config) => (set, get, api) =>
config(
(args) => {
console.log('Estado anterior:', get());
console.log('Ação sendo executada:', args);
set(args);
console.log('Novo estado:', get());
},
get,
api
);
// Store com Middleware
const useStore = create(
loggerMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
);</code></pre>
<p>Cada vez que você chama <code>increment()</code> ou <code>decrement()</code>, o middleware vai imprimir o estado anterior, a ação e o novo estado. Isso é extremamente útil para debug em desenvolvimento.</p>
<h3>Middleware Mais Complexo: Validação de Estado</h3>
<p>Um caso de uso real é validar se uma mudança de estado é válida antes de permitir:</p>
<pre><code class="language-javascript">import create from 'zustand';
// Middleware de Validação
const validatorMiddleware = (config) => (set, get, api) =>
config(
(args) => {
// Se for uma função, executa a validação
const newState = typeof args === 'function' ? args(get()) : args;
// Valida se count nunca fica negativo
if (newState.count < 0) {
console.warn('Count não pode ser negativo. Operação rejeitada.');
return;
}
set(args);
},
get,
api
);
const useCountStore = create(
validatorMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
);
// Tentativa de ir para -1 será bloqueada
useCountStore.getState().decrement(); // count = 0
useCountStore.getState().decrement(); // rejeitado, count continua 0</code></pre>
<h3>Combinando Múltiplos Middlewares</h3>
<p>Em projetos reais, você frequentemente precisa de vários middlewares. Zustand permite compor middlewares:</p>
<pre><code class="language-javascript">import create from 'zustand';
const loggerMiddleware = (config) => (set, get, api) =>
config(
(args) => {
console.log('Antes:', get());
set(args);
console.log('Depois:', get());
},
get,
api
);
const validatorMiddleware = (config) => (set, get, api) =>
config(
(args) => {
const newState = typeof args === 'function' ? args(get()) : args;
if (newState.count < 0) {
console.warn('Validação falhou');
return;
}
set(args);
},
get,
api
);
// Aplicar middlewares em sequência
const useStore = create(
validatorMiddleware(
loggerMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
)
);</code></pre>
<p>Note que a ordem importa — o middleware mais interno é executado primeiro. Neste exemplo, logger executa depois de validator, garantindo que apenas mudanças válidas sejam logadas.</p>
<h2>Persistência de Estado com Persist</h2>
<h3>Por Que Persistir Estado</h3>
<p>Muitas aplicações precisam manter estado entre sessões do usuário. Sem persistência, toda vez que o usuário recarrega a página, ele volta ao estado inicial. Zustand fornece um middleware built-in chamado <code>persist</code> que sincroniza o estado com localStorage (ou qualquer storage que você escolher).</p>
<h3>Configuração Básica do Persist</h3>
<pre><code class="language-javascript">import create from 'zustand';
import { persist } from 'zustand/middleware';
const useCartStore = create(
persist(
(set) => ({
items: [],
total: 0,
addItem: (item) => set((state) => ({
items: [...state.items, item],
total: state.total + item.price,
})),
clearCart: () => set({ items: [], total: 0 }),
}),
{
name: 'cart-store', // Nome da chave no localStorage
}
)
);</code></pre>
<p>Com essa simples configuração, sempre que o estado muda, ele é automaticamente salvo em <code>localStorage</code> com a chave <code>cart-store</code>. Quando a página é recarregada, o estado é restaurado automaticamente. Você não precisa fazer nada além disso — é totalmente transparente.</p>
<h3>Customizando o Storage e Comportamento</h3>
<p>Por padrão, persist usa localStorage, mas você pode usar qualquer storage que siga a interface <code>Storage</code>:</p>
<pre><code class="language-javascript">import create from 'zustand';
import { persist } from 'zustand/middleware';
// Usando sessionStorage em vez de localStorage
const useTemporaryStore = create(
persist(
(set) => ({
sessionData: 'inicial',
setSessionData: (data) => set({ sessionData: data }),
}),
{
name: 'temp-store',
storage: sessionStorage, // Muda para sessionStorage
}
)
);
// Usando um Storage Customizado (por exemplo, AsyncStorage do React Native)
const customStorage = {
getItem: async (name) => {
// Implementação para buscar de um storage customizado
return JSON.parse(await AsyncStorage.getItem(name));
},
setItem: async (name, value) => {
// Implementação para salvar em um storage customizado
await AsyncStorage.setItem(name, JSON.stringify(value));
},
removeItem: async (name) => {
await AsyncStorage.removeItem(name);
},
};
const useNativeStore = create(
persist(
(set) => ({
data: [],
addData: (item) => set((state) => ({ data: [...state.data, item] })),
}),
{
name: 'native-store',
storage: customStorage,
}
)
);</code></pre>
<h3>Selecionando Quais Partes do Estado Persistir</h3>
<p>Nem sempre você quer persistir todo o estado. Imagine que tem dados sensíveis ou temporários — você pode escolher explicitamente o que salvar:</p>
<pre><code class="language-javascript">import create from 'zustand';
import { persist } from 'zustand/middleware';
const useUserStore = create(
persist(
(set) => ({
user: { name: 'João', email: 'joao@email.com' },
password: '',
rememberMe: true,
setUser: (user) => set({ user }),
setPassword: (pwd) => set({ password: pwd }),
setRememberMe: (value) => set({ rememberMe: value }),
}),
{
name: 'user-store',
partialize: (state) => ({
user: state.user,
rememberMe: state.rememberMe,
// password NÃO será persistido — informação sensível
}),
}
)
);</code></pre>
<p>Com <code>partialize</code>, você fornece uma função que retorna apenas as partes do estado que deseja persistir. Isso é crucial para lidar com informações sensíveis como senhas, tokens ou dados temporários.</p>
<h3>Combinando Persist com Slices</h3>
<p>Agora vamos integrar persist com a estrutura de slices que vimos antes:</p>
<pre><code class="language-javascript">import create from 'zustand';
import { persist } from 'zustand/middleware';
const createCartSlice = (set) => ({
items: [],
total: 0,
addItem: (item) => set((state) => ({
items: [...state.items, item],
total: state.total + item.price,
})),
clearCart: () => set({ items: [], total: 0 }),
});
const createUserSlice = (set) => ({
user: null,
isAuthenticated: false,
setUser: (user) => set({ user, isAuthenticated: !!user }),
logout: () => set({ user: null, isAuthenticated: false }),
});
// Store com Persist, combinando slices
const useAppStore = create(
persist(
(set) => ({
...createCartSlice(set),
...createUserSlice(set),
}),
{
name: 'app-store',
// Persiste apenas dados relevantes
partialize: (state) => ({
items: state.items,
total: state.total,
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
}
)
);</code></pre>
<p>Essa abordagem combina a organização limpa dos slices com a persistência automática, criando uma solução robusta e escalável.</p>
<h3>Ciclo de Vida e Eventos do Persist</h3>
<p>Zustand persist oferece hooks de ciclo de vida que permite executar código quando a store é inicializada ou sincronizada:</p>
<pre><code class="language-javascript">import create from 'zustand';
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
data: [],
addData: (item) => set((state) => ({ data: [...state.data, item] })),
}),
{
name: 'my-store',
onRehydrateStorage: () => (state, error) => {
if (error) {
console.error('Erro ao recuperar estado:', error);
} else {
console.log('Estado recuperado com sucesso:', state);
}
},
}
)
);</code></pre>
<p>O callback <code>onRehydrateStorage</code> é executado quando o estado é restaurado do storage. Isso é útil para validar dados, migrar versões antigas ou sincronizar com um servidor.</p>
<h2>Conclusão</h2>
<p>Zustand oferece uma abordagem diferente e refrescante para gerenciamento de estado: simplicidade sem sacrificar funcionalidade. Os três pilares que exploramos — <strong>Slices para organização escalável</strong>, <strong>Middleware para interceptar e controlar mudanças</strong>, e <strong>Persist para manter estado entre sessões</strong> — formam uma base sólida para construir aplicações React robustas. O grande diferencial é que você não é forçado a usar nada disso desde o início; você começa simples e introduz esses padrões conforme a necessidade surge. Isso torna Zustand ideal tanto para projetos pequenos quanto para aplicações empresariais complexas.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/pmndrs/zustand" target="_blank" rel="noopener noreferrer">Documentação Oficial do Zustand</a></li>
<li><a href="https://docs.pmnd.rs/zustand/api/creating-a-store#middleware" target="_blank" rel="noopener noreferrer">Zustand Middleware Guide</a></li>
<li><a href="https://tkdodo.eu/blog/react-query-meets-react-router" target="_blank" rel="noopener noreferrer">React State Management Comparison</a></li>
<li><a href="https://docs.pmnd.rs/zustand/integrations/persisting-store-data" target="_blank" rel="noopener noreferrer">Zustand Persist Middleware</a></li>
<li><a href="https://www.smashingmagazine.com/2022/09/inline-svg-react-components-nextjs/" target="_blank" rel="noopener noreferrer">Modern JavaScript State Management Patterns</a></li>
</ul>
<p><!-- FIM --></p>