//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Decida quando usar React Context e quando usar Zustand. Context funciona para estado de baixa frequência, semelhante a temas. Zustand se destaca para estado de alta frequência e com múltiplos consumidores que necessita de re-renderização seletiva.
// Context: Bom para estado raramente mutável, com escopo de provider
const ThemeContext = createContext<"light" | "dark">("light");
// Zustand: Bom para estado global frequentemente mutável com muitos consumidores
const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
}));// ANTES: Estado baseado em Context (causa re-renderizações em todos os consumidores)
// context/app-context.tsx
"use client";
import { createContext, useContext, useState, useCallback, ReactNode } from "react";
interface AppState {
user: { name: string } | null;
theme: "light" | "dark";
notifications: string[];
sidebarOpen: boolean;
}
interface AppContextValue extends AppState {
setUser: (user: AppState["user"]) => void;
toggleTheme: () => void;
addNotification: (msg: string) => void;
toggleSidebar: () => void;
}
const AppContext = createContext<AppContextValue | null>(null);
export function AppProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AppState["user"]>(null);
const [theme, setTheme] = useState<AppState["theme"]>("light");
const [notifications, setNotifications] = useState<string[]>([]);
const [sidebarOpen, setSidebarOpen] = useState(true);
const toggleTheme = useCallback(() => {
setTheme((t) => (t === "light" ? "dark" : "light"));
}, []);
const addNotification = useCallback((msg: string) => {
setNotifications((n) => [...n, msg]);
}, []);
const toggleSidebar = useCallback(() => {
setSidebarOpen((s) => !s);
}, []);
// Problema: novo objeto a cada renderização -> todos os consumidores re-renderizam
return (
<AppContext.Provider
value={{
user, theme, notifications, sidebarOpen,
setUser, toggleTheme, addNotification, toggleSidebar,
}}
>
{children}
</AppContext.Provider>
);
}
export const useApp = () => {
const ctx = useContext(AppContext);
if (!ctx) throw new Error("useApp deve estar dentro de AppProvider");
return ctx;
};// DEPOIS: Store Zustand (re-renderizações seletivas, sem necessidade de provider)
// stores/app-store.ts
import { create } from "zustand";
interface AppStore {
user: { name: string } | null;
theme: "light" | "dark";
notifications: string[];
sidebarOpen: boolean;
setUser: (user: AppStore["user"]) => void;
toggleTheme: () => void;
addNotification: (msg: string) => void;
toggleSidebar: () => void;
}
export const useAppStore = create<AppStore>((set) => ({
user: null,
theme: "light",
notifications: [],
sidebarOpen: true,
setUser: (user) => set({ user }),
toggleTheme: () => set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
addNotification: (msg) => set((s) => ({ notifications: [...s.notifications, msg] })),
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));// Migração de componente
// ANTES:
function Header() {
const { user, theme, toggleTheme } = useApp();
// Re-renderiza quando notifications ou sidebarOpen também mudam
return <header>...</header>;
}
// DEPOIS:
function Header() {
const user = useAppStore((s) => s.user);
const theme = useAppStore((s) => s.theme);
const toggleTheme = useAppStore((s) => s.toggleTheme);
// Só re-renderiza quando user ou theme realmente mudam
return <header>...</header>;
}theme, ele re-renderiza quando notifications muda.createStore + useStore com um Context.setState do React.Quando Context ainda é a escolha certa:
// Tema, localidade ou status de autenticação que raramente mudam
// e naturalmente se escopam para uma subárvore
const ThemeContext = createContext<"light" | "dark">("light");
// Feature flags por segmento de rota
const FeatureFlagContext = createContext<Record<string, boolean>>({});
// Estado de formulário escopado para um único formulário
const FormContext = createContext<FormState | null>(null);Zustand com Context para escopo SSR:
import { createStore, useStore } from "zustand";
import { createContext, useContext, useRef } from "react";
type CounterStore = ReturnType<typeof createCounterStore>;
const createCounterStore = (initialCount = 0) =>
createStore<{ count: number; increment: () => void }>((set) => ({
count: initialCount,
increment: () => set((s) => ({ count: s.count + 1 })),
}));
const CounterContext = createContext<CounterStore | null>(null);
export function CounterProvider({
children,
initialCount,
}: {
children: React.ReactNode;
initialCount?: number;
}) {
const storeRef = useRef<CounterStore>();
if (!storeRef.current) {
storeRef.current = createCounterStore(initialCount);
}
return (
<CounterContext.Provider value={storeRef.current}>
{children}
</CounterContext.Provider>
);
}
export function useCounterStore<T>(selector: (s: { count: number; increment: () => void }) => T) {
const store = useContext(CounterContext);
if (!store) throw new Error("Missing CounterProvider");
return useStore(store, selector);
}createStore.useContext para chamadas useStore(selector). Faça isso incrementalmente.useMemo no valor do Context ajuda, mas não resolve o problema fundamental de re-renderização quando qualquer valor muda.devtools). O estado do Context aparece no React DevTools.| Abordagem | Prós | Contras |
|---|---|---|
| React Context | Embutido, sem dependências, escopo de árvore | Re-renderiza todos os consumidores a cada mudança |
| Zustand | Re-renderizações seletivas, sem provider, API simples | Singleton global, considerações de SSR |
| Jotai | Modelo atômico, provider opcional | Modelo mental diferente |
| Redux Toolkit | Maduro, ótimas DevTools | Boilerplate, curva de aprendizado íngreme |
| use(Context) + useMemo | Reduz algumas re-renderizações | Não resolve o problema central |
useContext nele.createStore para escopar stores por requisição.// Antes (Context):
const { user, theme } = useApp();
// Depois (Zustand):
const user = useAppStore((s) => s.user);
const theme = useAppStore((s) => s.theme);useContext para useStore(selector).useMemo reduz re-renderizações causadas pela re-renderização do provider.const StoreContext = createContext<MyStore | null>(null);
function Provider({ children }) {
const storeRef = useRef(createStore<State>((set) => ({ ... })));
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
);
}createStore (vanilla) + Context + useStore para instâncias por subárvore.null em useContext.devtools para se conectar ao Redux DevTools em vez disso.Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥