Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Context API no React: estado sem ficar passando props

Como criar contexto, envolver a árvore com o provider e ler com useContext — e por que todo componente abaixo re-renderiza quando o valor muda.

Rodolfo Mori13 min de leitura

Imagine a recepção de uma clínica lotada. O botão que troca o tema da tela está no fundo da aplicação, mas cada componente no caminho precisa receber tema e entregar tema ao próximo — mesmo sem usar essa informação. O código funciona, mas uma mudança simples vira uma fila de repasses.

A Context API resolve justamente esse transporte. Ao final deste artigo, você vai saber criar um contexto, escolher onde colocar o provider, ler o valor com useContext, reconhecer renders desnecessários e decidir quando contexto não é a ferramenta certa.

Pense no sistema de avisos de um prédio. createContext abre um canal; o provider — componente que fornece o valor — liga o alto-falante nos andares que estão abaixo dele; e useContext funciona como o aparelho que escuta aquele canal. Os componentes no corredor deixam de repetir a mensagem de porta em porta.

A analogia tem um limite importante: o alto-falante real pode guardar uma gravação, mas o contexto não guarda estado. Ele apenas transporta o value que recebe. Também não é global automaticamente: somente descendentes do provider ouvem o canal, e o React compara o valor anterior com o novo usando Object.is, uma comparação nativa do JavaScript, para atualizar os consumidores.

Todos os exemplos pertencem ao painel da clínica veterinária Pata Amiga: tema claro e escuro, fila de atendimento e um rodapé com o CRMV. As saídas vieram de execuções reais no Node 24.16.0 com React 19.2.8, usando react-dom/server ou react-dom/client dentro do jsdom 30.

Por que passar props por muitos componentes vira um problema?

Porque cada componente intermediário fica acoplado a dados que não usa. No painel, o tema nasce no componente de cima e o botão que o troca está três níveis abaixo; sem contexto, o caminho é este:

jsx
import { useState } from 'react';

function BotaoTema({ tema, alternar }) {
  return <button className={tema} onClick={alternar}>Trocar tema</button>;
}

function BarraDeAcoes({ tema, alternar }) {
  return <div className="acoes"><BotaoTema tema={tema} alternar={alternar} /></div>;
}

function Cabecalho({ tema, alternar }) {
  return (
    <header className={tema}>
      <h2>Pata Amiga</h2>
      <BarraDeAcoes tema={tema} alternar={alternar} />
    </header>
  );
}

function Painel() {
  const [tema, setTema] = useState('claro');
  const alternar = () => setTema((t) => (t === 'claro' ? 'escuro' : 'claro'));
  return <Cabecalho tema={tema} alternar={alternar} />;
}
<header class="claro"><h2>Pata Amiga</h2><div class="acoes"><button class="claro">Trocar tema</button></div></header>

Funciona, e a saída comprova isso. Leia o caminho de baixo para cima: Painel possui o estado; Cabecalho e BarraDeAcoes recebem duas props; somente BotaoTema usa essas props. Esse repasse por camadas recebe o nome de prop drilling. Em português simples, é passar uma encomenda por várias mãos que não precisam abri-la.

Contexto não torna props no React desnecessárias. Props continuam sendo a escolha mais clara quando pai e filho conversam diretamente. O incômodo aparece quando muitos intermediários apenas repassam o mesmo dado.

Experimente você mesmo

Antes de alterar o exemplo, preveja: se BarraDeAcoes deixar de receber tema e alternar, o botão ainda conseguirá trocar o tema? Agora retire mentalmente essas duas props apenas desse componente e siga o caminho do dado. A previsão correta é não: a encomenda deixa de chegar ao botão. Essa dependência do caminho inteiro é a dor que o contexto vai resolver.

Até aqui: prop drilling não significa que o código está quebrado; significa que componentes intermediários conheceram informações de que não precisam.

Como createContext, provider e useContext funcionam juntos?

createContext cria o canal, o provider oferece um valor a uma parte da árvore e useContext lê, no consumidor, o valor do provider mais próximo. Veja as três peças no menor exemplo completo:

jsx
import { createContext, useContext } from 'react';

const TemaContexto = createContext('claro');

function BotaoTema() {
  const tema = useContext(TemaContexto);
  return <button className={`botao botao-${tema}`}>Trocar tema</button>;
}

function Cabecalho() {
  return (
    <header>
      <h2>Pata Amiga</h2>
      <BotaoTema />
    </header>
  );
}

function Painel() {
  return (
    <TemaContexto.Provider value="escuro">
      <Cabecalho />
    </TemaContexto.Provider>
  );
}
<header><h2>Pata Amiga</h2><button class="botao botao-escuro">Trocar tema</button></header>

Leia o resultado como o usuário o receberia: o cabeçalho apareceu e o botão ganhou a classe botao-escuro. Cabecalho não recebeu nem repassou o tema. O BotaoTema, que realmente precisa do dado, sintonizou o canal com useContext.

O texto 'claro' passado a createContext é o valor default, ou valor de reserva. Ele só será usado se não houver provider correspondente acima. Quando existe mais de um provider do mesmo contexto, vale o mais próximo do consumidor — como ouvir o alto-falante do seu andar, não o de outro bloco.

Como usar o contexto diretamente no React 19?

No React 19, você pode oferecer o valor com <TemaContexto value={...}>; em versões anteriores, use <TemaContexto.Provider value={...}>. O teste abaixo compara as duas formas no mesmo ambiente:

jsx
const TemaContexto = createContext('claro');

function BotaoTema() {
  return <button>{useContext(TemaContexto)}</button>;
}

console.log('React', React.version);
console.log(renderToStaticMarkup(
  <TemaContexto value="escuro"><BotaoTema /></TemaContexto>
));
console.log(renderToStaticMarkup(
  <TemaContexto.Provider value="escuro"><BotaoTema /></TemaContexto.Provider>
));
console.log('Provider === contexto?', TemaContexto.Provider === TemaContexto);
console.log(String(TemaContexto.$$typeof), String(TemaContexto.Provider.$$typeof));
React 19.2.8 <button>escuro</button> <button>escuro</button> Provider === contexto? true Symbol(react.context) Symbol(react.context)

A primeira e a segunda renderização produziram o mesmo botão. Neste React 19.2.8, o log também mostrou que TemaContexto.Provider e TemaContexto apontam para o mesmo objeto. Esse detalhe explica a saída medida, mas o que o seu código deve usar como contrato público é mais simples: React 19 aceita o contexto como provider; React 18 exige a propriedade .Provider.

Em uma base nova que roda React 19, a forma curta reduz ruído. Em uma biblioteca que precisa aceitar React 18, mantenha .Provider enquanto essa compatibilidade for necessária.

Por que um objeto novo no provider pode causar novos renders?

Porque o React compara o value antigo e o novo com Object.is. Dois objetos literais com o mesmo conteúdo ainda são referências diferentes; se o provider renderizar de novo, os consumidores daquele contexto recebem a atualização.

Para enxergar o efeito sem adivinhar, os próximos exemplos usam um contador de render: uma variável do módulo registra quantas vezes cada componente foi calculado para produzir a interface.

jsx
const renders = {};
const conta = (nome) => { renders[nome] = (renders[nome] ?? 0) + 1; };
const mostrar = (quando) => console.log(quando, JSON.stringify(renders));

Neste exemplo, o provider guarda duas coisas separadas — o tema e o número de alertas da recepção. O Rodape só se importa com o tema, e ainda por cima está envolvido em memo:

jsx
function ProvedorDeTema({ children }) {
  const [tema, setTema] = useState('claro');
  const [alertas, setAlertas] = useState(0);
  const valor = { tema, alternar: () => setTema((t) => (t === 'claro' ? 'escuro' : 'claro')) };
  return (
    <TemaContexto.Provider value={valor}>
      <p>{alertas} alertas</p>
      <button id="alerta" onClick={() => setAlertas((n) => n + 1)}>Novo alerta</button>
      {children}
    </TemaContexto.Provider>
  );
}

const Rodape = memo(function Rodape() {
  conta('Rodape');
  const { tema } = useContext(TemaContexto);
  return <footer className={tema}>CRMV 12345</footer>;
});

Experimente você mesmo

Antes de executar o roteiro, preveja o placar do Rodape: o tema não muda, mas o contador de alertas muda duas vezes. Você espera 1, 1, 1 ou 1, 2, 3? Depois acompanhe os dois cliques em Novo alerta e confronte sua resposta com a saída. act() apenas garante que cada atualização terminou antes da leitura.

jsx
const raiz = createRoot(document.getElementById('raiz'));
await act(() => { raiz.render(<ProvedorDeTema><Rodape /></ProvedorDeTema>); });
mostrar('montou:          ');
await act(() => { document.getElementById('alerta').click(); });
mostrar('1 alerta novo:   ');
await act(() => { document.getElementById('alerta').click(); });
mostrar('2 alertas novos: ');
montou: {"Rodape":1} 1 alerta novo: {"Rodape":2} 2 alertas novos: {"Rodape":3}

A resposta medida foi 1, 2, 3. O tema não mudou, mas cada alerta renderizou ProvedorDeTema novamente; o objeto literal de valor ganhou outra referência. Para Object.is, o novo objeto é diferente do anterior, então o React notificou os componentes que escutam esse contexto.

memo compara props e pode evitar renders provocados por props iguais. Ele não impede que o próprio Rodape seja atualizado quando o contexto que consome recebe outro valor. No prédio, é como fechar a porta do escritório: isso reduz o barulho do corredor, mas não desliga o aparelho sintonizado no aviso.

Neste caso medido, alertas são alheios ao tema. Faz sentido estabilizar a função com useCallback e o objeto com useMemo. O roteiro de cliques continua igual; somente o provider muda:

jsx
function ProvedorDeTema({ children }) {
  const [tema, setTema] = useState('claro');
  const [alertas, setAlertas] = useState(0);
  const alternar = useCallback(() => {
    setTema((t) => (t === 'claro' ? 'escuro' : 'claro'));
  }, []);
  const valor = useMemo(() => ({ tema, alternar }), [tema, alternar]);
  return (
    <TemaContexto.Provider value={valor}>
      <p>{alertas} alertas</p>
      <button id="alerta" onClick={() => setAlertas((n) => n + 1)}>Novo alerta</button>
      {children}
    </TemaContexto.Provider>
  );
}
montou: {"Rodape":1} 1 alerta novo: {"Rodape":1} 2 alertas novos: {"Rodape":1}

O Rodape permaneceu em 1 render; o provider ainda processou os cliques, mas deixou de anunciar um valor de tema falsamente novo. alternar pode ficar estável porque a função setTema, devolvida por useState, tem identidade estável.

Isso não cria a regra de colocar todo objeto de provider em useMemo. Use essa otimização quando objetos ou funções mudam de identidade em renders alheios ao valor e a medição mostra trabalho desnecessário. Um valor primitivo ou um provider que só renderiza quando o dado compartilhado realmente muda pode não ganhar nada com a complexidade extra. O artigo de useMemo e useCallback aprofunda esse critério.

Até aqui: a comparação acontece no value completo; memo não bloqueia uma atualização de contexto, e memorizar só compensa quando existe uma mudança de identidade irrelevante para eliminar.

Quais componentes re-renderizam quando o contexto muda?

Os componentes que consomem aquele contexto recebem a atualização quando o provider oferece um valor diferente segundo Object.is. Componentes que não o consomem podem ficar parados, desde que nenhuma outra causa — como novas props do pai — os faça renderizar.

Agora o painel inteiro usa o provider estabilizado da seção anterior. Quatro componentes leem o contexto; TabelaDeVacinas não lê e está ali como vizinha:

jsx
function ProvedorDeTema({ children }) {
  const [tema, setTema] = useState('claro');
  const alternar = useCallback(() => {
    setTema((t) => (t === 'claro' ? 'escuro' : 'claro'));
  }, []);
  const valor = useMemo(() => ({ tema, alternar }), [tema, alternar]);
  return <TemaContexto.Provider value={valor}>{children}</TemaContexto.Provider>;
}

function Cabecalho() {
  conta('Cabecalho');
  const { tema } = useContext(TemaContexto);
  return <h2 className={tema}>Pata Amiga</h2>;
}

function BotaoTema() {
  conta('BotaoTema');
  const { alternar } = useContext(TemaContexto);
  return <button id="trocar" onClick={alternar}>Trocar tema</button>;
}

function FilaDeAtendimento() {
  conta('FilaDeAtendimento');
  const { tema } = useContext(TemaContexto);
  return <ul className={tema}><li>Nina — 14:30</li><li>Thor — 15:00</li></ul>;
}

function Rodape() {
  conta('Rodape');
  const { tema } = useContext(TemaContexto);
  return <footer className={tema}>CRMV 12345</footer>;
}

function TabelaDeVacinas() {
  conta('TabelaDeVacinas');
  return <table><tbody><tr><td>V10</td><td>anual</td></tr></tbody></table>;
}

O roteiro monta o painel e clica duas vezes no botão de trocar tema. Repare que o clique é buscado pelo id: se você pegar o primeiro <button> da página, corre o risco de clicar em outro e medir a coisa errada.

jsx
function Painel() {
  return (
    <ProvedorDeTema>
      <Cabecalho />
      <BotaoTema />
      <FilaDeAtendimento />
      <Rodape />
      <TabelaDeVacinas />
    </ProvedorDeTema>
  );
}

const clicar = () => act(() => { document.getElementById('trocar').click(); });

const raiz = createRoot(document.getElementById('raiz'));
await act(() => { raiz.render(<Painel />); });
mostrar('montou: ');
await clicar();
mostrar('1 clique:');
await clicar();
mostrar('2 cliques:');
montou: {"Cabecalho":1,"BotaoTema":1,"FilaDeAtendimento":1,"Rodape":1,"TabelaDeVacinas":1} 1 clique: {"Cabecalho":2,"BotaoTema":2,"FilaDeAtendimento":2,"Rodape":2,"TabelaDeVacinas":1} 2 cliques: {"Cabecalho":3,"BotaoTema":3,"FilaDeAtendimento":3,"Rodape":3,"TabelaDeVacinas":1}

Leia o placar em três passos:

  1. TabelaDeVacinas permaneceu em 1. Neste desenho, ela chega ao provider como o mesmo elemento em children, criado pelo Painel. Como não consome o contexto nem recebe novas props, a troca de tema não exige outro render dela. children ajudou aqui, mas não é um escudo universal: outro pai ou outra prop ainda poderia provocar um render.
  2. Os quatro consumidores chegaram a 3. A API nativa de useContext não recebe um seletor de campos. Quando o value muda, cada componente que chama useContext(TemaContexto) é notificado, mesmo que aproveite apenas uma parte do objeto.
  3. BotaoTema também chegou a 3. Ele usa somente alternar, cuja referência ficou estável, mas está sintonizado no mesmo canal que carrega tema. Separar os dois canais elimina esse trabalho específico.
ProvedorDeTema TabelaDeVacinas Cabecalho BotaoTema FilaDeAtend. Rodape 1 render 3 renders 3 renders 3 renders 3 renders chama useContext: acorda a cada troca de tema não lê o contexto: fica parado como children

Como separar valores e ações em dois contextos?

Crie um contexto para o dado que muda e outro para as funções estáveis. Assim, um componente que apenas dispara uma ação não precisa ouvir cada mudança do dado. No painel, o tema ocupa um canal e as ações ocupam outro:

jsx
const TemaValor = createContext('claro');
const TemaAcoes = createContext(null);

function ProvedorDeTema({ children }) {
  const [tema, setTema] = useState('claro');
  const acoes = useMemo(() => ({
    alternar: () => setTema((t) => (t === 'claro' ? 'escuro' : 'claro')),
    definir: (novo) => setTema(novo),
  }), []);
  return (
    <TemaAcoes.Provider value={acoes}>
      <TemaValor.Provider value={tema}>{children}</TemaValor.Provider>
    </TemaAcoes.Provider>
  );
}

function BotaoTema() {
  conta('BotaoTema');
  const { alternar } = useContext(TemaAcoes);
  return <button id="trocar" onClick={alternar}>Trocar tema</button>;
}

Os outros três componentes só trocam useContext(TemaContexto) por useContext(TemaValor). O roteiro de cliques é o mesmo da seção anterior:

montou: {"Cabecalho":1,"BotaoTema":1,"FilaDeAtendimento":1,"Rodape":1} 1 clique: {"Cabecalho":2,"BotaoTema":1,"FilaDeAtendimento":2,"Rodape":2} 2 cliques: {"Cabecalho":3,"BotaoTema":1,"FilaDeAtendimento":3,"Rodape":3}

O BotaoTema permaneceu em 1 render depois de dois cliques. Ele fala no canal das ações, mas não escuta o canal do tema. Cabecalho, FilaDeAtendimento e Rodape chegaram a 3 porque exibem o tema e realmente precisam acompanhar as mudanças.

O array de dependências do useMemo das ações está vazio neste exemplo porque as funções usam apenas setTema, cuja identidade é estável. Se uma ação passar a depender de uma prop ou de outro valor reativo, essa dependência deverá entrar no array. Não copie [] como receita sem conferir o corpo das funções.

Até aqui: dividir contextos funciona como separar dois sistemas de som. Quem precisa apenas enviar comandos não precisa ouvir os avisos de tema.

O que acontece quando um componente usa contexto sem provider?

Ele recebe o valor default passado a createContext; se esse default for null ou undefined e o código esperar um objeto, a leitura falha. Primeiro, veja o caso em que existe um objeto de reserva:

jsx
const TemaContexto = createContext({ tema: 'claro', alternar: () => {} });

function Rodape() {
  const { tema } = useContext(TemaContexto);
  return <footer className={tema}>CRMV 12345</footer>;
}

console.log(renderToStaticMarkup(<Rodape />));
console.log(renderToStaticMarkup(
  <TemaContexto value={{ tema: 'escuro', alternar: () => {} }}>
    <Rodape />
  </TemaContexto>
));
<footer class="claro">CRMV 12345</footer> <footer class="escuro">CRMV 12345</footer>

A primeira linha usou o default claro; a segunda encontrou o provider e usou escuro. A reserva pode ser conveniente em um contexto com fallback real, mas também pode esconder um provider esquecido: uma parte da tela volta ao tema claro sem mostrar erro.

Uma alternativa é escolher null como sentinela — um valor deliberado que marca “provider ausente”. Assim o esquecimento deixa uma pista imediata:

jsx
const TemaContexto = createContext(null);

function Rodape() {
  const { tema } = useContext(TemaContexto);
  return <footer className={tema}>CRMV 12345</footer>;
}

// o Rodape ficou fora do provider
console.log(renderToStaticMarkup(<Rodape />));
file:///private/tmp/pata-amiga/Rodape.mjs:6 const { tema } = useContext(TemaContexto); ^

TypeError: Cannot destructure property ‘tema’ of ‘useContext(…)’ as it is null. at Rodape (file:///private/tmp/pata-amiga/Rodape.mjs:6:11) at Object.react_stack_bottom_frame (/private/tmp/pata-amiga/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9808:18) at renderWithHooks (/private/tmp/pata-amiga/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5062:19) at renderElement (/private/tmp/pata-amiga/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5497:23) … (mais 6 quadros, do react-dom até a chamada em Rodape.mjs) Node.js v24.16.0

Esse TypeError vem da desestruturação do JavaScript: o código tentou retirar tema de null. O sintoma é a falha em Rodape; a causa é o componente estar fora do provider; a primeira verificação é subir pela árvore e localizar onde ProvedorDeTema foi montado. Se o acesso fosse contexto.tema, a mensagem seria Cannot read properties of null, mas a causa continuaria igual.

A correção mais clara é centralizar a leitura em um Hook próprio. Ele valida a sentinela e troca o erro genérico por uma instrução útil:

jsx
const TemaContexto = createContext(undefined);

function useTema() {
  const valor = useContext(TemaContexto);
  if (valor === undefined) {
    throw new Error('useTema() precisa de um <ProvedorDeTema> acima na árvore');
  }
  return valor;
}

function Rodape() {
  const { tema } = useTema();
  return <footer className={tema}>CRMV 12345</footer>;
}
Error: useTema() precisa de um <ProvedorDeTema> acima na árvore at useTema (file:///private/tmp/pata-amiga/useTema.mjs:8:11) at Rodape (file:///private/tmp/pata-amiga/useTema.mjs:13:20) at Object.react_stack_bottom_frame (/private/tmp/pata-amiga/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9808:18) at renderWithHooks (/private/tmp/pata-amiga/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5062:19) … (mais 6 quadros dentro do react-dom) Node.js v24.16.0

Agora o rastro aponta useTema e informa que falta <ProvedorDeTema> acima. Expor apenas useTema e ProvedorDeTema, mantendo TemaContexto privado, ajuda todos os consumidores a passar pela mesma validação.

Context API é um gerenciador de estado?

Não. Context API transporta um valor pela árvore; quem guarda e atualiza o dado pode ser useState, useReducer, uma prop ou outra fonte. Neste exemplo, o contexto recebe sempre a mesma referência e a mutação não agenda render algum:

jsx
const TemaContexto = createContext(null);

// objeto solto no módulo: o contexto só carrega a referência dele
const configuracao = { tema: 'claro' };

function Rodape() {
  const { tema } = useContext(TemaContexto);
  return <footer className={tema}>CRMV 12345</footer>;
}

function Painel() {
  return (
    <TemaContexto value={configuracao}>
      <button onClick={() => { configuracao.tema = 'escuro'; }}>Trocar tema</button>
      <Rodape />
    </TemaContexto>
  );
}

const raiz = createRoot(document.getElementById('raiz'));
await act(() => { raiz.render(<Painel />); });
console.log('antes: ', document.querySelector('footer').outerHTML);
await act(() => { document.querySelector('button').click(); });
console.log('objeto:', JSON.stringify(configuracao));
console.log('depois:', document.querySelector('footer').outerHTML);
antes: <footer class="claro">CRMV 12345</footer> objeto: {"tema":"escuro"} depois: <footer class="claro">CRMV 12345</footer>

O objeto passou a conter escuro, como mostra a segunda linha, mas o rodapé continuou com a classe claro. Mutar uma referência não avisa o React nem substitui o value; é a mesma base de atualizar estado sem mutar. useState é uma forma simples de guardar esse dado e solicitar a nova renderização, como mostra a lição de estado no React.

Aqui termina a analogia do alto-falante: contexto leva o aviso aos andares, mas não escreve nem atualiza o aviso. Seu papel é substituir o valor em um escopo da árvore. Providers aninhados demonstram isso; o mais próximo do consumidor vence:

jsx
const TemaContexto = createContext('claro');

function Etiqueta({ texto }) {
  return <span className={useContext(TemaContexto)}>{texto}</span>;
}

console.log(renderToStaticMarkup(
  <TemaContexto value="escuro">
    <Etiqueta texto="painel" />
    <TemaContexto value="alto-contraste">
      <Etiqueta texto="modal de emergencia" />
    </TemaContexto>
    <Etiqueta texto="rodape" />
  </TemaContexto>
));
<span class="escuro">painel</span><span class="alto-contraste">modal de emergencia</span><span class="escuro">rodape</span>

Leia a ordem: a etiqueta painel ouviu escuro do provider externo; a etiqueta do modal encontrou primeiro o provider interno e recebeu alto-contraste; ao sair desse escopo, rodape voltou a ouvir escuro. Um provider interno não altera o restante do prédio — apenas a subárvore abaixo dele.

Com isso na mão, dá para decidir com critério:

situação ferramenta por quê
duas telas irmãs precisam do mesmo dado subir o estado para o pai comum não precisa de canal nenhum
um componente no meio só repassa a prop children e composição resolve prop drilling sem re-render extra
tema, idioma, usuário logado, moeda Context API muda pouco, é lido em muitos lugares
muitas transições no mesmo estado useReducer dentro do provider centraliza as regras, e o dispatch é estável
lista grande que muda a cada tecla biblioteca de estado com seletor contexto acorda todos os consumidores

A última linha marca o limite. A API nativa de useContext não oferece um seletor para pedir “avise apenas quando tema mudar”. Quando o value muda, todos os consumidores daquele contexto são notificados. Se isso aparece como custo real no Profiler — o medidor do React DevTools —, divida os contextos ou avalie uma ferramenta que ofereça seletores. useMemo evita mudanças de identidade alheias; ele não elimina uma atualização legítima do valor compartilhado.

O que estudar depois da Context API?

O próximo passo é combinar o provider com useReducer: o reducer concentra as regras de mudança e o dispatch mantém identidade estável. Depois, o artigo sobre re-render no React explica como o React decide recalcular componentes. A trilha de React organiza esses assuntos; se o clique e a atualização ainda parecerem mágicos, revise antes os eventos do React.

Experimente você mesmo

Antes de montar o teste final, preveja: se um consumidor lê tema e outro lê usuario no mesmo objeto de contexto, qual deles renderiza quando somente o tema muda? Registre ambos com console.count, altere o tema e confira. Depois divida os valores em dois contextos e repita. Com a API nativa de useContext, a previsão é: os dois recebem a primeira atualização; separados, o consumidor de usuário deixa de ser atualizado por causa do contexto de tema. Ele ainda pode renderizar por outro motivo, como uma nova prop ou um pai que também renderizou.

Essa comparação fecha a ideia do alto-falante: primeiro escolha quem realmente precisa ouvir o canal; só depois pense em estabilizar ou separar a transmissão.

  • react
  • context api
  • usecontext
  • estado global
  • provider

Perguntas frequentes

Context API substitui Redux ou Zustand?
Para tema, idioma, usuário logado e outras coisas que mudam pouco, sim. Para estado que muda muitas vezes por segundo e é lido por dezenas de componentes, não: o contexto não tem seletor, então cada mudança acorda todo mundo que chama useContext.
Posso usar vários contextos no mesmo projeto?
Pode, e é o recomendado. Um contexto por assunto — tema, usuário, carrinho — em vez de um contexto gigante com tudo dentro. Contextos separados fazem cada mudança acordar menos componentes.
Onde eu coloco o provider?
No menor pedaço da árvore que precisa do valor. Se só as telas internas usam o usuário logado, o provider vai em volta das telas internas, não em volta do app inteiro.
useContext funciona dentro de if ou de laço?
Não. useContext é um hook e obedece às regras dos hooks: sempre no topo do componente, sempre na mesma ordem, nunca dentro de condição, laço ou função aninhada.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0 com React 19.2.8, react-dom 19.2.8 e jsdom 30.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. React — useContext — react.dev
  2. React — createContext — react.dev
  3. React — Passing Data Deeply with Context — react.dev

Continue por aqui