Context API no React: compartilhando estado sem prop drilling
Prop drilling é o nome do desconforto que aparece quando um valor mora num componente alto na árvore, é usado por um componente baixo, e precisa atravessar todo mundo no meio só para chegar lá. O estado do tema do site é um exemplo típico: ele costuma viver no layout raiz, mas o componente que alterna o tema é uma folha qualquer dentro do header.
App (tem o estado `theme`)
└── Layout (recebe theme, repassa)
└── Header (recebe theme, repassa)
└── ThemeToggle (finalmente usa theme!)
Cada nível intermediário precisa carregar uma prop que não usa, e qualquer mudança na assinatura — renomear theme para colorScheme, adicionar um campo — propaga pela cadeia inteira. É o tipo de fricção que parece bobagem em um exemplo de quatro componentes e vira inferno em uma aplicação real.
A Context API
A Context API é o mecanismo do próprio React para furar essa árvore. Um valor publicado num Provider fica disponível para qualquer descendente, sem passar por ninguém no meio — independente de quantos níveis existam entre o Provider e o consumidor.
Criando o contexto
import { createContext } from 'react'
type Theme = 'light' | 'dark'
interface ThemeContextValue {
theme: Theme
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextValue | null>(null)createContext recebe o valor padrão. Usar null aqui em vez de um objeto vazio é proposital: o contexto só faz sentido dentro do ThemeProvider, e o tipo ThemeContextValue | null força quem consome a tratar o caso de "não tem Provider acima".
O Provider
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('light')
const toggleTheme = () => {
const next: Theme = theme === 'dark' ? 'light' : 'dark'
setTheme(next)
localStorage.setItem('theme', next)
document.documentElement.classList.toggle('dark', next === 'dark')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}O Provider é um componente normal: segura o estado, expõe o que quiser pelo value, e qualquer componente abaixo na árvore passa a ter acesso direto a esse objeto.
O hook de consumo
'use client'
import { useContext } from 'react'
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
return ctx
}O if (!ctx) substitui um crash silencioso ("cannot read property theme of null") por uma mensagem que aponta direto para o erro real — alguém esqueceu de envolver o componente com o Provider. Esse pequeno hook costuma valer mais que o useContext cru exatamente por causa dessa linha.
O componente final
'use client'
import { useTheme } from './ThemeProvider'
export function ThemeToggle() {
const { theme, toggleTheme } = useTheme()
return (
<button onClick={toggleTheme}>
{theme === 'dark' ? 'Claro' : 'Escuro'}
</button>
)
}O ThemeToggle não tem props relacionadas ao tema. Não importa quantos níveis existam entre ele e o Provider — useTheme resolve.
Onde o Provider é registrado
Com App Router, o Provider mora no layout.tsx raiz, envolvendo tudo:
import { ThemeProvider } from '@/components/ThemeProvider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}A partir daqui, qualquer componente cliente abaixo dessa árvore pode consumir o contexto. Vale lembrar que useContext só faz sentido em Client Components; é por isso que tanto o ThemeProvider quanto o useTheme levam 'use client' no topo.
Quando usar Context (e quando não usar)
Context se encaixa bem em estado global e de baixa frequência de atualização: tema, idioma, usuário autenticado, configuração de interface. Para estado local de um formulário ou de uma lista, useState no próprio componente continua sendo o caminho.
A objeção mais comum é que toda mudança no value re-renderiza todos os consumidores. É verdade — e o problema é tipicamente menor do que blogs e tutoriais sugerem. Para tema, que muda umas duas vezes por sessão, o custo é nulo. O cuidado vale quando o value muda dezenas de vezes por segundo (posição de drag, scroll, animação): aí Context atrapalha mesmo, e ferramentas como Zustand, Jotai ou Redux passam a fazer sentido.