//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Instale o Zustand, crie um store com create e consuma-o em qualquer componente sem providers ou wrappers de context.
npm install zustand// stores/counter-store.ts
import { create } from "zustand";
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));"use client";
import { useCounterStore } from "@/stores/counter-store";
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}// stores/todo-store.ts
import { create } from "zustand";
interface Todo {
id: string;
text: string;
done: boolean;
}
interface TodoState {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
removeTodo: (id: string) => void;
clearCompleted: () => void;
}
export const useTodoStore = create<TodoState>((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: crypto.randomUUID(), text, done: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter((t) => t.id !== id),
})),
clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((t) => !t.done),
})),
}));// components/todo-app.tsx
"use client";
import { useState } from "react";
import { useTodoStore } from "@/stores/todo-store";
export function TodoApp() {
const [input, setInput] = useState("");
const todos = useTodoStore((s) => s.todos);
const addTodo = useTodoStore((s) => s.addTodo);
const toggleTodo = useTodoStore((s) => s.toggleTodo);
const removeTodo = useTodoStore((s) => s.removeTodo);
const clearCompleted = useTodoStore((s) => s.clearCompleted);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim()) {
addTodo(input.trim());
setInput("");
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.done ? "line-through" : "none" }}>
{todo.text}
</span>
</label>
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
<button onClick={clearCompleted}>Clear completed</button>
<p>{todos.filter((t) => !t.done).length} items remaining</p>
</div>
);
}create retorna um hook React que se inscreve no store. O store em si é um objeto JavaScript vanilla gerenciado fora do React.set mescla o estado parcial no estado atual (mesclagem rasa por padrão).set pode aceitar um objeto (mesclado) ou uma função (state) => partialState (para atualizações baseadas no estado atual).Obtendo o estado completo (não recomendado para desempenho):
const { count, increment } = useCounterStore();
// Re-renderiza em QUALQUER mudança de estadoUsando o store fora do React:
// Acessa o estado diretamente (sem hooks)
const count = useCounterStore.getState().count;
// Inscreve-se em mudanças
const unsub = useCounterStore.subscribe((state) => {
console.log("Count changed:", state.count);
});Substitui o estado em vez de mesclar:
set({ count: 0 }, true); // Segundo argumento `true` substitui o estado inteirocreate<State>().set é tipado para aceitar Partial<State> ou (state: State) => Partial<State>.import { create, StoreApi } from "zustand";
type Store = StoreApi<CounterState>;useStore() sem um seletor) faz com que o componente re-renderize a cada mudança de estado. Sempre use seletores.set realiza uma mesclagem rasa (shallow merge). Objetos aninhados devem ser espalhados manualmente: set({ user: { ...state.user, name: "new" } }).set é síncrono. A atualização do estado e o re-render ocorrem no mesmo tick (agrupados pelo React 18+).| Abordagem | Prós | Contras |
|---|---|---|
| Zustand | Sem providers, API mínima, seletores rápidos | Singleton em SSR, curva de aprendizado para middleware |
| React Context | Integrado, sem dependências | Re-renderiza todos os consumidores, sem seletores |
| Redux Toolkit | Ecossistema maduro, DevTools | Boilerplate, configuração complexa |
| Jotai | Modelo atômico, de baixo para cima | Modelo mental diferente, muitos átomos para gerenciar |
De uma aplicação SaaS de produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: Store de autenticação
// Arquivo: src/stores/auth.ts
import { create } from 'zustand'
import { User } from '@supabase/supabase-js'
interface AuthState {
user: User | null
loading: boolean
setUser: (user: User | null) => void
setLoading: (loading: boolean) => void
}
export const useAuthStore = create<AuthState>()((set) => ({
user: null,
loading: true,
setUser: (user) => set({ user }),
setLoading: (loading) => set({ loading }),
}))
// Uso com seletor (previne re-renderizações de mudanças de estado não relacionadas):
// const user = useAuthStore((state) => state.user)
// NÃO: const { user } = useAuthStore() // inscreve-se em TODAS as mudançasO que isso demonstra em produção:
create<AuthState>()((set) => ...) não são um erro de digitação. O primeiro () é necessário ao usar genéricos TypeScript com Zustand, e também habilita o encadeamento de middleware (por exemplo, create<AuthState>()(persist(devtools((set) => ...)))).set({ user }) faz uma mesclagem rasa (shallow merge), não uma substituição. Apenas o campo user é atualizado enquanto loading permanece intocado. Este é o comportamento padrão do Zustand.useAuthStore((s) => s.user) se inscreve apenas no campo user. Usar const { user } = useAuthStore() sem um seletor se inscreve no store inteiro, causando re-renderizações sempre que qualquer campo muda (incluindo loading).useAuthStore.getState(), inclusive em código não-React como utilitários de API ou middleware.create retorna um hook React (por exemplo, useCounterStore) que se inscreve no store.useCounterStore((state) => state.count).(state) => state.count se inscrevem apenas nessa fatia do estado.const { count } = useStore()) se inscreve no store inteiro, causando re-renderizações a cada mudança.set realiza uma mesclagem rasa (shallow merge) do estado parcial no estado atual.true como segundo argumento: set({ count: 0 }, true).// Lê o estado diretamente
const count = useCounterStore.getState().count;
// Inscreve-se em mudanças
const unsub = useCounterStore.subscribe((state) => {
console.log("Count:", state.count);
});set({ count: 0 }) define um valor estático, independentemente do estado atual.set((state) => ...) calcula o próximo estado com base no estado atual, o que é necessário para incrementos, toggles e atualizações derivadas.useStore() sem um seletor se inscreve em todas as propriedades do store.set faz uma mesclagem rasa apenas no nível superior.set((s) => ({ user: { ...s.user, name: "new" } })).interface CounterState {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}));create<State>().set aceita Partial<State> ou (state: State) => Partial<State>.() é necessário ao usar genéricos TypeScript com middleware.create<State>()(persist(devtools((set) => ...))).create<State>((set) => ...) (invocação única) é suficiente.set é síncrono. A atualização do estado ocorre imediatamente.set podem ser agrupadas em uma única renderização.Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥