Passar props através de camadas de componentes desinteressados é um trabalho tedioso. Um dia você está lançando uma funcionalidade e, no outro, está editando seis arquivos apenas para renomear uma única prop. Isso é o prop drilling, em resumo: um pai tem os dados, um filho profundo precisa deles, e cada componente entre eles se torna um mensageiro. O app ainda funciona, mas a base de código torna-se frágil. Remova uma camada intermediária e metade da árvore colapsa. Altere um tipo e o TypeScript reclamará em três diretórios diferentes. A Context API do React existe para remover esses intermediários completamente.

Como o Prop Drilling realmente se parece

Imagine uma estrutura básica de app padrão. Você tem um componente App que busca o usuário atual. Dentro do App vive um Layout, dentro do Layout está um Sidebar, dentro do Sidebar aninha-se um Navigation e, finalmente, dentro do Navigation, você encontra o UserAvatar que realmente precisa do objeto do usuário.

Seu código acaba ficando assim:

function App() {
  const user = { name: 'Aarav', role: 'admin' };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <Navigation user={user} />;
}

function Navigation({ user }) {
  return <UserAvatar user={user} />;
}

Layout, Sidebar e Navigation não fazem nada com esse objeto de usuário, exceto passá-lo adiante. Eles acumulam props que não possuem, suas interfaces incham e testá-los exige a criação de mocks para dados que eles nunca tocam. O verdadeiro problema é a rapidez com que isso se espalha. Adicione uma flag isLoggedIn, uma string locale ou um valor de theme, e o mesmo desfile se repete.

Como a Context API muda o jogo

Pense na Context API como um roteador WiFi. Em vez de passar cabos longos por todos os cômodos para alcançar cada dispositivo, o roteador envia um sinal pelo ar. Qualquer dispositivo ao alcance pode se conectar diretamente. Em termos de React, o roteador é o Provider, o sinal é o seu estado ou dado, e o dispositivo é qualquer componente aninhado que chama useContext.

A configuração possui três elementos principais:

  • React.createContext() constrói o canal de dados.
  • O Provider envolve uma seção da sua árvore e transmite um valor.
  • O hook useContext permite que os descendentes recebam esse valor sem tocar em props intermediárias.

Você ainda tem uma árvore, mas os ramos entre a raiz e a folha não precisam mais concordar com um contrato de mensageiro.

Construindo um Contexto do Zero

Vamos construir um exemplo concreto com configurações de tema, já que a maioria dos apps precisa de modo claro ou escuro em algum momento.

Primeiro, crie o objeto de contexto. Este é o cano:

import { createContext, useState, useMemo } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export default ThemeContext;

Em seguida, envolva sua aplicação no provider. Geralmente isso acontece próximo à raiz:

import { ThemeProvider } from './ThemeContext';

function App() {
  return (
    <ThemeProvider>
      <Layout />
    </ThemeProvider>
  );
}

Agora, qualquer descendente pode captar o sinal diretamente. Aqui está um botão de alternância (toggle) enterrado profundamente na interface:

import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}
    >
      Current theme: {theme}
    </button>
  );
}

Observe que Layout, Sidebar e Navigation nunca veem a prop theme. Eles renderizam normalmente, e o ThemeToggle busca o que precisa diretamente do contexto. A fiação é invisível por fora, que é exatamente o objetivo.

Onde o Contexto realmente se encaixa

O Context funciona melhor para dados que muitos componentes distantes compartilham, mas que nenhum pai individual possui de forma limpa. Bons candidatos incluem:

  • Configurações de tema, como modo claro ou escuro, cores de destaque ou escala de fonte.
  • Estado de autenticação, como o objeto do usuário atual, status de login ou expiração de sessão.
  • Idioma e localidade para internacionalização.
  • Dados do carrinho de compras que devem permanecer sincronizados em um badge no cabeçalho, um menu suspenso de mini-carrinho e uma página de checkout.

Resista à tentação de despejar cada pedaço de estado local no Context. Um input de formulário dois níveis abaixo não precisa de uma transmissão global. Guarde o Context para preocupações transversais reais e deixe o resto como props comuns.

Armadilhas de Performance e como evitá-las

O Context não é gratuito. Quando um valor de contexto é atualizado, cada componente conectado a esse contexto renderiza novamente, mesmo que a parte do valor que lhe interessa não tenha mudado. O erro clássico é passar um novo objeto literal para o Provider em cada renderização do pai.

Em nosso exemplo de tema, toda vez que o ThemeProvider renderiza novamente porque seu próprio pai foi atualizado, a expressão { theme, setTheme } cria um objeto totalmente novo. O React vê uma nova referência e todos os consumidores são atualizados. Se o seu tema raramente muda, mas o estado do seu app muda com frequência, você está pagando por renderizações desnecessárias.

A solução é dupla.

Divida seus contextos pela frequência de atualização. Um UserContext que muda uma vez por login não deve compartilhar um provider com um NotificationContext que atualiza a cada poucos segundos. Mantenha-os separados para que dados estáticos não viajem no mesmo trem de re-renderização que dados voláteis.

Envolva o valor em useMemo quando o valor for um objeto ou array. Forneça ao React uma referência estável:

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const value = useMemo(() => ({ theme, setTheme }), [theme]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

Agora, a identidade do objeto só muda quando o theme realmente muda. Componentes descendentes que dependem do contexto, mas estão protegidos por React.memo mais abaixo, pularão o trabalho.

Erros que fazem você perder tempo

Os dois erros que ainda aparecem em código de produção são fáceis de prevenir.

Primeiro, esquecer de exportar o próprio contexto. Se você exportar apenas o wrapper do Provider e mantiver o objeto de contexto privado, um desenvolvedor escrevendo uma nova funcionalidade não poderá chamar useContext sem refatorar seu módulo. Exporte o contexto para que os consumidores possam importar tanto o provider quanto o hook de consumo de forma limpa.

Segundo, chamar useContext fora do Provider correspondente. Se o ThemeToggle for renderizado em um ramo da árvore que não está envolvido pelo ThemeProvider, o hook retornará o valor padrão passado para o createContext, ou undefined se você não passou nenhum. Isso leva a falhas silenciosas como cannot read property of undefined. Você pode se prevenir contra isso atribuindo um valor padrão sensato ou lançando um erro claro logo no início da chamada do hook.

Context vs Redux: Mantenha a Simplicidade

Você nem sempre precisa de Redux. Para projetos de médio porte, o Context combinado com useState ou useReducer cobre a maior parte do compartilhamento de estado. O Redux brilha quando você precisa de time-travel debugging, middlewares complexos ou transações globais que devem sofrer rollback em sequência. Se toda a sua história de estado for um objeto de usuário, uma string de tema e um array de carrinho, uma biblioteca de store adicionará um boilerplate que você nunca aproveitará.

Dito isso, o Context não é um sistema completo de gerenciamento de estado por si só. Ele não fornece um snapshot global único e não agrupa atualizações entre contextos não relacionados. Use-o como um substituto para prop drilling, não como um sistema operacional para toda a sua camada de dados.

A Principal Lição

Pare de passar props através de componentes que não se importam com elas. Crie contextos focados para os dados que realmente abrangem sua árvore, envolva os providers em um nível alto o suficiente para cobrir os consumidores e sempre estabilize o objeto de valor ao passar coleções ou funções. A Context API mantém seu código React direto: as props permanecem locais, os dados globais viajam "sem fio" e os limites dos seus componentes permanecem limpos.