Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { createContext, use, useState, useCallback, type ReactNode } from "react";
// Contexto dividido: separa estado de dispatch
const CountStateContext = createContext<number>(0);
const CountDispatchContext = createContext<{
increment: () => void;
decrement: () => void;
} | null>(null);
function CountProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
const dispatch = useMemo(() => ({
increment: () => setCount((c) => c + 1),
decrement: () => setCount((c) => c - 1),
}), []);
return (
<CountStateContext value={count}>
<CountDispatchContext value={dispatch}>
{children}
</CountDispatchContext>
</CountStateContext>
);
}Quando usar isso: Quando você tem um contexto que causa re-renderizações em componentes que precisam apenas de parte do valor do contexto. Divida estado e ações em contextos separados, escopoe o contexto para subárvores e use seletores para ler apenas o que você precisa.
import {
createContext,
use,
useState,
useMemo,
useCallback,
memo,
type ReactNode,
} from "react";
// --- Contexto dividido: Estado do tema vs ações ---
interface ThemeState {
mode: "light" | "dark";
accentColor: string;
fontSize: number;
}
interface ThemeActions {
toggleMode: () => void;
setAccentColor: (color: string) => void;
setFontSize: (size: number) => void;
}
const ThemeStateContext = createContext<ThemeState>({
mode: "light",
accentColor: "#3b82f6",
fontSize: 16,
});
const ThemeActionsContext = createContext<ThemeActions | null>(null);
function ThemeProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<ThemeState>({
mode: "light",
accentColor: "#3b82f6",
fontSize: 16,
});
// Objeto de ações estável - nunca causa re-renderizações em consumidores apenas de ações
const actions = useMemo<ThemeActions>(
() => ({
toggleMode: () =>
setState((s) => ({
...s,
mode: s.mode === "light" ? "dark" : "light",
})),
setAccentColor: (color) =>
setState((s) => ({ ...s, accentColor: color })),
setFontSize: (size) =>
setState((s) => ({ ...s, fontSize: size })),
}),
[]
);
return (
<ThemeStateContext value={state}>
<ThemeActionsContext value={actions}>
{children}
</ThemeActionsContext>
</ThemeStateContext>
);
}
// Hooks personalizados com verificações de segurança
function useThemeState() {
return use(ThemeStateContext);
}
function useThemeActions() {
const actions = use(ThemeActionsContext);
if (!actions) throw new Error("useThemeActions deve estar dentro de ThemeProvider");
return actions;
}
// --- Componentes demonstrando consumo seletivo ---
// Re-renderiza apenas quando o estado do tema muda
const ThemeIndicator = memo(function ThemeIndicator() {
const { mode, accentColor } = useThemeState();
console.log("ThemeIndicator rendered");
return (
<div className="flex items-center gap-2">
<div
className="w-4 h-4 rounded-full"
style={{ backgroundColor: accentColor }}
/>
<span>{mode} mode</span>
</div>
);
});
// Re-renderiza apenas quando o contexto de ações muda (nunca, porque é memoizado)
const ThemeToggleButton = memo(function ThemeToggleButton() {
const { toggleMode } = useThemeActions();
console.log("ThemeToggleButton rendered");
return (
<button onClick={toggleMode} className="px-3 py-1 border rounded">
Toggle Theme
</button>
);
});
// --- Padrão de contexto com escopo ---
interface NotificationContextValue {
notifications: Notification[];
add: (message: string) => void;
dismiss: (id: string) => void;
}
const NotificationContext = createContext<NotificationContextValue | null>(null);
function useNotifications() {
const ctx = use(NotificationContext);
if (!ctx) throw new Error("useNotifications deve estar dentro de NotificationProvider");
return ctx;
}
interface Notification {
id: string;
message: string;
}
function NotificationProvider({ children }: { children: ReactNode }) {
const [notifications, setNotifications] = useState<Notification[]>([]);
const add = useCallback((message: string) => {
const id = crypto.randomUUID();
setNotifications((prev) => [...prev, { id, message }]);
setTimeout(() => {
setNotifications((prev) => prev.filter((n) => n.id !== id));
}, 5000);
}, []);
const dismiss = useCallback((id: string) => {
setNotifications((prev) => prev.filter((n) => n.id !== id));
}, []);
const value = useMemo(
() => ({ notifications, add, dismiss }),
[notifications, add, dismiss]
);
return (
<NotificationContext value={value}>
{children}
</NotificationContext>
);
}
// --- Layout completo do aplicativo ---
function App() {
return (
<ThemeProvider>
<NotificationProvider>
<header className="flex justify-between p-4 border-b">
<ThemeIndicator />
<ThemeToggleButton />
</header>
<main className="p-6">
<ContentArea />
</main>
</NotificationProvider>
</ThemeProvider>
);
}O que isso demonstra:
ThemeStateContext e ThemeActionsContext são separadosuseMemo - consumidores apenas de ações nunca re-renderizammemo em componentes folha para evitar re-renderizações de renderizações de componentes paiuseMemo no objeto de ações garante que sua referência nunca mude, tornando o contexto de ações estável.useMemo no objeto de valor só é útil se você tiver vários campos de estado e quiser evitar re-renderizações quando campos não relacionados mudam - mas como o objeto é recriado quando qualquer campo muda, ele é mais eficaz com contextos divididos por domínio.use() do React 19 pode ler contexto condicionalmente (dentro de instruções if), ao contrário do useContext.| Padrão | O Que Resolve |
|---|---|
| Dividir estado/ações | Consumidores apenas de ações (botões, formulários) não re-renderizam em mudanças de estado |
| Objeto de valor memoizado | Evita re-renderizações quando a referência do objeto mudaria, mas os conteúdos são os mesmos |
| Provedor com escopo | Contexto disponível apenas para a subárvore que precisa dele |
| Hook personalizado com erro | Captura bugs de provedor ausente em tempo de desenvolvimento |
| Valor padrão em createContext | Permite usar contexto sem um provedor (útil para padrões de tema) |
Padrão de seletor com loja externa - assine apenas a parte que você precisa:
import { useSyncExternalStore } from "react";
// Usando useSyncExternalStore para leituras baseadas em seletores
function useStoreSelector<T, S>(store: Store<T>, selector: (state: T) => S): S {
return useSyncExternalStore(
store.subscribe,
() => selector(store.getSnapshot()),
() => selector(store.getServerSnapshot())
);
}
// Re-renderiza apenas quando `user.name` muda
function UserName() {
const name = useStoreSelector(appStore, (s) => s.user.name);
return <span>{name}</span>;
}Contexto com reducer - para transições de estado complexas:
const TodoDispatchContext = createContext<React.Dispatch<TodoAction> | null>(null);
const TodoStateContext = createContext<TodoState>({ items: [] });
function TodoProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(todoReducer, { items: [] });
return (
<TodoStateContext value={state}>
<TodoDispatchContext value={dispatch}>
{children}
</TodoDispatchContext>
</TodoStateContext>
);
}createContext<T>(defaultValue) quando o contexto puder funcionar sem um provedor.createContext<T | null>(null) quando um provedor for necessário e verifique se é nulo no hook personalizado.ThemeActions em vez de ThemeActions | null) após a verificação de nulo.Contexto único com estado e ações misturados - Toda mudança de estado re-renderiza todo consumidor, mesmo aqueles que apenas chamam ações. Correção: Divida em contextos de estado e ação separados.
Valor de contexto instável - Criar uma nova literal de objeto na renderização do provedor (value={{ a, b }}) faz com que todo consumidor re-renderize. Correção: Use useMemo para estabilizar a referência do valor.
Provedor muito alto na árvore - Colocar um provedor que muda frequentemente na raiz do aplicativo re-renderiza toda a árvore de consumidores. Correção: Escope os provedores para a menor subárvore que precisa deles.
Divisão excessiva de contexto - Criar dezenas de contextos minúsculos adiciona complexidade e aninhamento de provedores. Correção: Divida por frequência de atualização (coisas que mudam juntas devem ficar juntas). Use Zustand ou Jotai para assinaturas granulares.
Valores de contexto padrão ocultando bugs - Um valor padrão significativo significa que o contexto funciona sem um provedor, o que pode mascarar um provedor ausente. Correção: Use padrão null + hook personalizado com throw para provedores necessários.
| Abordagem | Compromisso |
|---|---|
| Contexto dividido | Zero dependências; esforço manual de divisão |
| Zustand | Seletores automáticos, sem provedores; dependência extra |
| Jotai | Estado atômico, re-renderizações granulares; modelo mental diferente |
| Redux + useSelector | Ecossistema maduro, depuração com viagem no tempo; boilerplate |
useSyncExternalStore | Funciona com qualquer loja externa; API de nível inferior |
| Sinais (futuro) | Reatividade granular; ainda não no React |
useMemo(() => actions, []), sua referência nunca muda.const actions = useMemo<ThemeActions>(
() => ({
toggleMode: () => setState((s) => ({ ...s, mode: s.mode === "light" ? "dark" : "light" })),
setAccentColor: (color) => setState((s) => ({ ...s, accentColor: color })),
}),
[]
);use() pode ser chamado condicionalmente (dentro de instruções if), ao contrário do useContext().use() também funciona com promessas para busca de dados baseada em Suspense.useContext() ainda funciona no React 19, mas o use() é a alternativa mais flexível.a e b não tenham mudado.useMemo ou divida em contextos separados.createContext(defaultValue), os componentes funcionarão sem um provedor.createContext<T | null>(null) e lance um erro no hook personalizado quando o contexto for nulo.const MyContext = createContext<MyState | null>(null);
function useMyContext(): MyState {
const ctx = use(MyContext);
if (!ctx) throw new Error("useMyContext must be within MyProvider");
return ctx;
}| null como padrão e verifique se é nulo no hook personalizado.MyState (não nulo) após a verificação.useSyncExternalStore assina uma loja externa e lê uma fatia selecionada do estado.const TodoStateContext = createContext<TodoState>({ items: [] });
const TodoDispatchContext = createContext<React.Dispatch<TodoAction> | null>(null);
function TodoProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(todoReducer, { items: [] });
return (
<TodoStateContext value={state}>
<TodoDispatchContext value={dispatch}>{children}</TodoDispatchContext>
</TodoStateContext>
);
}dispatch é estável (o React garante isso), então consumidores apenas de dispatch nunca re-renderizam.Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥