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

Buscando dados

Um componente que mostra dados de um servidor não tem esses dados no momento em que renderiza pela primeira vez. Ele precisa pedir por eles, esperar e então mostrar algo assim que a resposta chegar, ou se a requisição falhar no caminho. Este capítulo cobre o padrão que apps React usam para isso: fazer fetch dentro de um effect e armazenar o resultado, um estado de carregamento e um erro em useState.

Aqui está um componente Profile que busca um usuário por URL. A renderização acontece primeiro, e alcançar o servidor logo depois é exatamente o momento para o qual useEffect existe:

jsx
function Profile({ url }) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  useEffect(() => {
    let active = true
    setData(null)
    setError(null)
    fetch(url)
      .then(r => {
        if (!r.ok) throw new Error(`Request failed: ${r.status}`)
        return r.json()
      })
      .then(d => { if (active) setData(d) })
      .catch(e => { if (active) setError(e) })
    return () => { active = false }
  }, [url])

  if (error) return <p>Failed to load</p>
  if (!data) return <p>Loading...</p>
  return <h1>{data.name}</h1>
}

Se fetch, promises ou .then ainda são estranhos para você, o manual de JavaScript cobre tudo isso do zero em JavaScript Assíncrono; tudo aqui é construído diretamente sobre aquelas bases.

useEffect é o hook que executa código depois que React renderizou um componente, em vez de durante a renderização em si. Você passa a ele uma função para executar e um array de dependências, a lista de valores que devem fazer com que ele rode novamente quando mudarem, aqui apenas url. Profile começa com dois pedaços de estado: data para o resultado e error para qualquer coisa que deu errado. No topo do effect, setData(null) e setError(null) limpam o que a execução anterior deixou para trás. Isso importa por duas razões: sem isso, uma mudança de url continuaria mostrando o perfil antigo em vez de "Loading...", já que data ainda contém o último resultado bem-sucedido, e uma requisição que funciona depois que uma anterior falhou nunca limparia o erro antigo, já que nada nunca coloca error de volta para null. Depois fetch retorna uma promise para a resposta. fetch só rejeita essa promise em uma falha de rede, então um 404 ou 500 ainda resolve normalmente como um objeto de resposta, é por isso que o primeiro .then verifica r.ok e lança antes da resposta chegar em .json(). Pule essa verificação e uma requisição falhada iria direto para data como se tivesse conseguido. Uma vez que a verificação passa, .json() lê o corpo e o resultado cai em data através de setData. Se qualquer coisa disso rejeitar, .catch coloca o problema em error em vez disso. Lá no JSX, error e data são verificados como qualquer outro estado: um erro renderiza uma mensagem, sem dados ainda renderiza uma mensagem de carregamento, e dados renderizam a UI real.

Não há uma variável de carregamento separada aqui. Enquanto data e error forem ambos ainda null, o componente está entre a requisição saindo e algo voltando, então if (!data) return <p>Loading...</p> cobre essa lacuna por si só. Alguns componentes adicionam um boolean isLoading dedicado para controle mais fino, mas derivar carregamento do estado que você já tem é frequentemente mais simples, e é um valor a menos para manter em sincronia.

A variável active dentro do effect é uma flag de limpeza. useEffect pode retornar uma função, e React chama essa função pouco antes do effect rodar novamente e quando o componente faz unmount. Aqui, a função retornada coloca active para false, e tanto .then quanto .catch verificam antes de chamar setData ou setError. Sem essa verificação, uma requisição ainda em voo quando url muda, ou quando o componente sai da tela inteiramente, eventualmente resolveria e tentaria atualizar estado que não pertence mais ao que está mostrando. A flag transforma uma resposta atrasada em um no-op em vez de um bug.

Buscar dados assim manualmente vale a pena entender, porque é o padrão em que quase tudo mais é construído. Em um app real você geralmente vai procurar por uma biblioteca de busca de dados como React Query, ou carregamento de dados integrado do framework (Next.js e frameworks similares lidam com isso na camada de roteamento), em vez de escrever useEffect e alguns pedaços de estado para cada requisição. Essas ferramentas lidam com cache, retentativas e os problemas de timing abaixo para você. O padrão neste capítulo é o que elas são construídas sobre.

Race conditions entre requisições sobrepostas são a borda afiada que vale a pena nomear diretamente. Se url muda rapidamente, alguém digitando em uma caixa de busca, ou clicando entre perfis, o componente lança uma nova requisição antes que a anterior tenha resolvido, e respostas de rede não chegam confiavelmente na ordem em que foram enviadas. Sem a flag de limpeza, uma resposta lenta para a primeira requisição pode chegar depois de uma resposta rápida para a segunda, sobrescrevendo data com conteúdo obsoleto para um url que você nem está mostrando mais. É exatamente o que active previne: cada execução do effect fecha sobre sua própria variável active, então uma requisição de uma execução anterior nunca pode escrever no estado da renderização atual assim que sua própria limpeza foi acionada.

AbortController é a outra ferramenta para isso, e vai além: ela cancela a própria requisição em voo em vez de apenas ignorar uma resposta obsoleta que chega depois.

jsx
useEffect(() => {
  const controller = new AbortController()
  setData(null)
  setError(null)
  fetch(url, { signal: controller.signal })
    .then(r => {
      if (!r.ok) throw new Error(`Request failed: ${r.status}`)
      return r.json()
    })
    .then(setData)
    .catch(e => { if (e.name !== 'AbortError') setError(e) })
  return () => controller.abort()
}, [url])

Esta versão ainda precisa dos mesmos dois ajustes que o effect acima: resetar data e error no topo da execução, então uma tentativa pode realmente limpar uma falha anterior ou um perfil obsoleto, e verificar r.ok antes de analisar o corpo, já que fetch trata um 404 ou 500 como uma requisição completada em vez de uma rejeição. AbortController apenas substitui o trabalho que a flag de limpeza estava fazendo, cancelando uma requisição que não é mais desejada, então não faz nada sobre como estado é carregado entre execuções ou como fetch trata uma resposta HTTP falhada por si só. Ambas as abordagens corrigem o problema de ordenação, mas abortar também poupa à rede o incômodo de terminar uma requisição que ninguém quer mais.

A direção mais nova, a partir do React 19, é ler dados assíncrona com o hook use dentro de um componente envolvido em <Suspense>, o que deixa React lidar com o estado pendente e de carregamento na camada do framework em vez de manualmente em cada componente. use não é tipicamente usado sozinho. Geralmente é pareado com um framework ou uma biblioteca de dados que gerencia cache e o ciclo de vida da requisição por baixo. Beyond the basics pega isso junto com o resto do ecossistema de busca de dados mais amplo.

JunoFetch em um effect, observe os estados O padrão a lembrar: coloque o fetch dentro de useEffect, mantenha data e error em estado, e verifique-os no seu JSX para decidir o que mostrar. Carregamento é o momento quando ambos ainda estão vazios. A flag active impede que uma resposta atrasada defina estado em um componente que não está mais mostrando esses dados.
JunoFetch em um effect, observe os estados Fetch dentro de useEffect, armazene o resultado e qualquer erro em estado, e renderize diretamente fora deles em vez de adicionar um boolean de carregamento separado se você não precisar de um. Retorne uma função de limpeza que muda uma flag active, para que uma resposta obsoleta de um url anterior ou um componente desmontado não possa sobrescrever o estado atual. Depois de uma requisição única, procure por React Query ou carregamento de dados do seu framework em vez de fazer isso à mão em todo lugar.
JunoFetch em um effect, observe os estados Cada execução do effect é dona de seu próprio closure active, que é o que o mantém seguro contra respostas fora de ordem quando url muda rapidamente; AbortController faz o mesmo trabalho e também cancela a requisição desperdiçada. Trate fetching bruto de useEffect como o mecanismo que vale a pena entender uma vez. Apps reais se apoiam em uma biblioteca de dados ou no hook use com Suspense para requisições reais, ambos construídos exatamente sobre este padrão por baixo.

Próximo: Refs e o DOM, a válvula de escape para os trabalhos que precisam do nó do DOM em si.