Skip to content
This page has been auto-translated and may contain errors.View in English

Context

Elevar o estado funciona bem quando os componentes que compartilham um valor ficam próximos. Fica difícil quando não ficam. Um valor que fica perto do topo da árvore e é necessário seis níveis abaixo precisa ser passado por cada componente no meio, e cada um deles tem que aceitar e repassar uma prop que nunca usa. Esse repasse é prop drilling, o problema que Componentes compostos enfrentaram e não conseguiram resolver com Children.map. Context é a forma nativa do React de sair dessa: um componente fornece um valor, e qualquer componente abaixo dele pode ler esse valor diretamente, não importa quantas camadas estejam no meio.

Pense nisso como teleportar os dados. Os componentes no meio nunca veem isso e nunca repassam; suas listas de props continuam focadas no que eles fazem.

Criando um context e fornecendo um valor

Context tem três partes: criar, fornecer um valor e ler o valor. Criar acontece uma vez, no nível superior de um arquivo, fora de qualquer componente. Você geralmente também vai exportar, porque os componentes que leem isso costumam ficar em outros arquivos.

jsx
import { createContext } from 'react'

export const ThemeContext = createContext()

export default function App() {
  return (
    <ThemeContext value="light">
      <Header />
      <Button />
    </ThemeContext>
  )
}

Renderizar o próprio objeto context como um componente faz com que tudo dentro dele seja um consumidor em espera. A prop value é a carga útil: aqui é a string "light", mas pode ser qualquer valor JavaScript, um objeto, um array, até uma função. O que você colocar lá é o que os leitores abaixo vão receber. A prop tem que ser escrita como value; esse nome faz parte da API.

O posicionamento importa. O provedor não precisa envolver todo seu app, e geralmente não deveria. Coloque-o ao redor da menor subárvore que cobre cada componente que precisa do valor. Se só um canto da UI se importa com o tema, forneça o tema no ancestral comum daquele canto e deixe o resto da árvore de fora.

Nota de versão

Antes do React 19 um context não podia ser renderizado diretamente; você renderizava <ThemeContext.Provider value="light"> em vez disso. React 19 tornou o próprio objeto context renderizável como o provedor. A forma .Provider ainda funciona e é o que você vai ver na maioria dos codebases existentes e nas aulas do curso, mas React planeja descontinuá-la em uma versão futura e fornece um codemod para fazer a mudança, então use a forma simples <ThemeContext> em código novo.

Lendo o valor com useContext

Qualquer componente abaixo do provedor lê o valor com useContext, o hook apresentado pela primeira vez em Hooks, passando para ele o objeto context para que React saiba qual context ler. É por isso que o context é exportado.

jsx
import { useContext } from 'react'
import { ThemeContext } from './App'

function Header() {
  const theme = useContext(ThemeContext)

  return (
    <header className={`${theme}-theme`}>
      <h1>{theme === 'light' ? 'Light' : 'Dark'} Theme</h1>
    </header>
  )
}

useContext(ThemeContext) retorna o que o ThemeContext provedor mais próximo acima deste componente está mantendo atualmente: aqui, a string "light". Não há repasse de prop entre App e Header, e componentes entre eles em uma árvore maior nem mencionariam o tema. Um app pode ter vários contexts ao mesmo tempo, cada um criado separadamente e cada um lido passando seu próprio objeto para useContext.

Tornando vivo: state mais context

Um value="light" codificado nunca muda. O padrão que torna context útil o combina com state: state é dono do valor e o atualiza, context o entrega. Passe um objeto que contém tanto o valor atual quanto uma função que o muda, e todo consumidor pode ler ou atualizar o tema de qualquer lugar abaixo do provedor.

jsx
export const ThemeContext = createContext()

export default function App() {
  const [theme, setTheme] = useState('light')

  function toggleTheme() {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext value={{ theme, toggleTheme }}>
      <Header />
      <Button />
    </ThemeContext>
  )
}

function Button() {
  const { theme, toggleTheme } = useContext(ThemeContext)

  return (
    <button className={`${theme}-theme`} onClick={toggleTheme}>
      Switch theme
    </button>
  )
}

Quando o botão é clicado, toggleTheme roda, o state em App atualiza, App renderiza de novo, e o provedor passa o novo objeto para baixo. Todo componente lendo o context renderiza de novo com o valor fresco. Consumidores desestrutura apenas as propriedades que precisam.

Esse truque de escopo também funciona em pequena escala. Um componente como um menu dropdown pode renderizar um provedor ao redor de seus próprios filhos, dando aos pedaços dentro dele um state compartilhado que nunca vaza para o resto da página. Aqui está o menu de Componentes compostos, terminado:

jsx
const MenuContext = createContext()

function Menu({ children }) {
  const [open, setOpen] = useState(false)
  const toggle = () => setOpen(prevOpen => !prevOpen)

  return <MenuContext value={{ open, toggle }}>{children}</MenuContext>
}

function MenuButton({ children }) {
  const { toggle } = useContext(MenuContext)

  return <button onClick={toggle}>{children}</button>
}

Menu é dono de open e coloca tanto isso quanto toggle no context; MenuButton chega lá procurando o toggle com useContext, e um MenuDropdown escrito do mesmo jeito lê open para decidir se renderiza. Nada no meio repassa nada, então um chamador pode envolver o botão em quantos divs de layout quiser e o menu continua funcionando.

Exportar o context puro funciona, mas a maioria dos codebases envolve o padrão em duas peças: um componente provedor que é dono do state, e um hook customizado que lê o context e falha com barulho quando mal usado.

jsx
const ThemeContext = createContext(null)

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

  function toggleTheme() {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext>
  )
}

export function useTheme() {
  const context = useContext(ThemeContext)
  if (context === null) {
    throw new Error('useTheme must be used within a ThemeProvider')
  }
  return context
}

Agora consumidores escrevem const { theme } = useTheme() e nunca importam o objeto context, o que mantém a superfície pública do módulo exatamente duas coisas, o provedor e o hook. O argumento para createContext é o fallback que React passa de volta quando nenhum provedor fica acima do consumidor, então null aqui significa "ninguém forneceu isso," e é isso que a verificação testa. Sem isso o consumidor falha ao desestruturar um valor null, e o stack trace aponta para o consumidor em vez do provedor faltante.

Quando o value de um provedor muda, React renderiza de novo todo componente que lê esse context, não importa o quão fundo ele fica. "Muda" significa uma comparação Object.is que falha, que é onde o object literal value morde: o objeto passado para value no código acima constrói um objeto fresco em cada render do dono do provedor, então cada render daquele dono renderiza de novo cada consumidor, independente se theme realmente mudou.

Quando o dono renderiza de novo só porque o value mudou, como App faz aqui, memoização não compra nada. Vale a pena quando o dono renderiza de novo por razões não relacionadas e arrasta cada consumidor junto com ele. Envolver o objeto em useMemo é metade do conserto: toggleTheme é uma função fresca em cada render, então o memo recalcula toda vez a menos que aquela função seja estabilizada também, com useCallback ou passando o já-estável setTheme no lugar de um wrapper.

Duas ferramentas estruturais importam mais que memoização. Primeiro, divida contexts pela frequência de atualização: um valor que muda uma vez por sessão e um valor que muda a cada digitação não pertencem ao mesmo provedor, porque o rápido arrasta os consumidores do lento junto com ele.

Segundo, lembre que context é um mecanismo de entrega em vez de um gerenciador de state. Resolve "este valor é necessário longe daqui." Não faz nada sobre como as atualizações são agrupadas, derivadas ou persistidas. Quando o state compartilhado de um app cresce além de um tema e um objeto de usuário, esse é o ponto onde um store dedicado como Redux Toolkit ou Zustand vale a pena.

Como cada store chega aos seus componentes

Os dois entregam seus stores de formas diferentes: um app Redux Toolkit envolve a árvore em <Provider store={store}> de react-redux, que passa o store para hooks abaixo dele através de context, enquanto o setup padrão de Zustand exporta um hook de nível de módulo que qualquer componente chama diretamente, sem nenhum provedor envolvido.

JunoContext pula as camadas do meio Quando um valor tem que viajar longe na árvore, passar por cada componente fica cansativo rápido.

Context deixa um componente perto do topo dizer "aqui está o valor" e deixa qualquer componente abaixo chegar até lá e pegá-lo diretamente com useContext. Os componentes no meio nunca tocam nele.

Combine com state e o valor pode mudar também: mantenha o valor e sua função de atualização juntos no provedor, e qualquer consumidor pode ler ou mudá-lo.

JunoContext pula as camadas do meio Crie um context no nível do módulo, renderize-o como um provedor com um value, e leia abaixo com useContext. Torne-o vivo passando state e seu atualizador juntos em um objeto.

Em código real, envolva o provedor em seu próprio componente e exponha um hook no estilo useTheme que lança fora do provedor; consumidores ganham uma API limpa e erros aparecem com uma mensagem legível.

JunoContext pula as camadas do meio Todo consumidor renderiza de novo quando o value fornecido falha em Object.is, e um object literal inline falha nela em cada render do provedor, então memoize o value onde a frequência de atualização torna isso importante. Divida contexts que atualizam em taxas diferentes, e escope provedores à menor subárvore que precisa deles.

Trate context como entrega para valores distantes; uma vez que state compartilhado precisa de gerenciamento real, procure um store, quer ele rode sobre context como Redux Toolkit quer pule como Zustand.

Próximo: Render props e componentes headless, onde um componente fornece comportamento e deixa você fornecer a marcação.