Context: estado global sem prop drilling.
Compartilhando estado sem passar props manualmente em vários níveis. createContext, Provider, useContext e custom hooks para Context.
A lição anterior mostrou que prop drilling — passar props por componentes intermediários que não as usam — se torna problemático em árvores mais profundas. Context é a solução do React para esse problema: um mecanismo que disponibiliza um valor para qualquer componente na subárvore sem precisar passá-lo manualmente por cada nível.
O que é Context
Context não armazena estado — ele distribui estado. Você ainda usa useState para gerenciar o valor; Context cuida de torná-lo acessível sem prop drilling:
// sem Context — tema precisa passar por cada nível
<App tema={tema}>
<Layout tema={tema}>
<Header tema={tema}>
<Nav tema={tema}>
<ThemeToggle tema={tema} /> {/* finalmente usa */}
</Nav>
</Header>
</Layout>
</App>
// com Context — ThemeToggle consome diretamente do contexto
<TemaProvider>
<Layout>
<Header>
<Nav>
<ThemeToggle /> {/* acessa o tema via useContext */}
</Nav>
</Header>
</Layout>
</TemaProvider> Os casos ideais para Context: tema da aplicação, idioma, usuário autenticado, preferências globais — valores que mudam com pouca frequência e que muitos componentes na árvore precisam.
Criando e usando Context
A receita completa tem três partes: createContext, Provider e useContext.
import { createContext, useState, useContext, type ReactNode } from "react";
// 1. criar o Context com um valor padrão
// o valor padrão é usado quando o componente não está dentro de um Provider
interface TemaContextValue {
tema: "claro" | "escuro";
alternarTema: () => void;
}
const TemaContext = createContext<TemaContextValue | null>(null);
// 2. criar o Provider — componente que fornece o valor para a subárvore
interface TemaProviderProps {
children: ReactNode;
}
export function TemaProvider({ children }: TemaProviderProps) {
const [tema, setTema] = useState<"claro" | "escuro">(() => {
const salvo = localStorage.getItem("tema");
return salvo === "escuro" ? "escuro" : "claro";
});
function alternarTema() {
setTema(prev => {
const novo = prev === "claro" ? "escuro" : "claro";
localStorage.setItem("tema", novo);
document.documentElement.setAttribute("data-theme", novo);
return novo;
});
}
return (
<TemaContext.Provider value={{ tema, alternarTema }}>
{children}
</TemaContext.Provider>
);
}
// 3. custom hook para consumir o Context
// encapsula o useContext e adiciona verificação de uso correto
export function useTema(): TemaContextValue {
const ctx = useContext(TemaContext);
if (!ctx) {
throw new Error("useTema deve ser usado dentro de <TemaProvider>");
}
return ctx;
} O createContext(null) com o tipo TemaContextValue | null é intencional: o valor padrão null garante que qualquer uso do Context fora de um Provider seja detectável (o custom hook lança um erro). Se você usar um valor padrão “real”, o erro de uso errado passa silenciosamente.
Usando o Provider e o hook
Envolva a aplicação com o Provider uma vez — normalmente no componente raiz:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { TemaProvider } from "./context/TemaContext";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<TemaProvider>
<App />
</TemaProvider>
</StrictMode>
); Qualquer componente dentro de TemaProvider pode acessar o tema sem receber props:
import { useTema } from "../context/TemaContext";
export function ThemeToggle() {
const { tema, alternarTema } = useTema();
return (
<button
className="botao-tema"
onClick={alternarTema}
aria-label={`Mudar para tema ${tema === "claro" ? "escuro" : "claro"}`}
>
{tema === "claro" ? "🌙" : "☀️"}
</button>
);
} import { useTema } from "../context/TemaContext";
import { ThemeToggle } from "./ThemeToggle";
export function Header() {
const { tema } = useTema();
return (
<header className={`header header-${tema}`}>
<span className="logo">aprenda-frontend</span>
<ThemeToggle />
</header>
);
} Context para curtidas
Outro caso concreto: o estado de curtidas precisa ser compartilhado entre HomePage (exibe os cards) e potencialmente ArticlePage (exibe o botão de curtir do artigo completo). Em vez de elevar para o App e fazer prop drilling por todas as páginas, use Context:
import { createContext, useState, useContext, type ReactNode } from "react";
interface CurtidasContextValue {
curtidos: Set<number>;
toggleCurtida: (id: number) => void;
}
const CurtidasContext = createContext<CurtidasContextValue | null>(null);
export function CurtidasProvider({ children }: { children: ReactNode }) {
const [curtidos, setCurtidos] = useState<Set<number>>(() => {
const salvo = localStorage.getItem("curtidos");
return salvo ? new Set(JSON.parse(salvo)) : new Set();
});
function toggleCurtida(id: number) {
setCurtidos(prev => {
const novos = new Set(prev);
if (novos.has(id)) {
novos.delete(id);
} else {
novos.add(id);
}
localStorage.setItem("curtidos", JSON.stringify([...novos]));
return novos;
});
}
return (
<CurtidasContext.Provider value={{ curtidos, toggleCurtida }}>
{children}
</CurtidasContext.Provider>
);
}
export function useCurtidas() {
const ctx = useContext(CurtidasContext);
if (!ctx) throw new Error("useCurtidas deve ser usado dentro de <CurtidasProvider>");
return ctx;
} Agora qualquer componente pode curtir um artigo com useCurtidas() — sem receber curtidos e toggleCurtida como props em cada nível.
Quando não usar Context
Context faz todos os componentes que consomem re-renderizarem quando o valor muda. Para estado que muda com alta frequência, isso pode ser caro:
// ❌ posição do mouse muda centenas de vezes por segundo
// todos os consumidores re-renderizam a cada movimento
const MouseContext = createContext({ x: 0, y: 0 });
function MouseProvider({ children }: { children: ReactNode }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
window.addEventListener("mousemove", e => setPos({ x: e.clientX, y: e.clientY }));
}, []);
return <MouseContext.Provider value={pos}>{children}</MouseContext.Provider>;
}
// qualquer componente que consome MouseContext re-renderiza a cada pixel movido A regra prática: Context é para estado que muda com pouca frequência e que muitos componentes na árvore precisam. Para:
- Estado local (só um componente usa):
useStateno próprio componente - Estado em 2–3 níveis: prop drilling — simples e explícito
- Estado frequente e localizado:
useStateno nível mais baixo, passar via props apenas para quem usa - Estado global e de baixa frequência: Context — tema, idioma, usuário autenticado
Resumo
- Context distribui estado, não armazena —
useStateainda gerencia o valor. - Receita:
createContext→Providercom o valor →useContextno consumidor. - Custom hook para encapsular
useContext: interface limpa e verificação de uso fora do Provider. - Quando o
valuedo Provider muda, todos os consumidores re-renderizam — tenha isso em mente para estado frequente. - Context é ideal para: tema, idioma, usuário autenticado, curtidas globais. Não para: posição do mouse, scroll, estado local de um componente.
- Antes de Context: avalie se prop drilling de 2–3 níveis não é mais simples.
O que é o Context no React e para que serve?
O que acontece quando o value de um Provider muda?
Por que é boa prática criar um custom hook para encapsular useContext?
Quando Context NÃO é a solução certa?
Aula concluída
Quase lá.