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 possui esses dados no momento em que renderiza pela primeira vez. Precisa pedir, esperar e então mostrar algo quando a resposta chega, ou se a requisição falhar pelo caminho. Este capítulo cobre o padrão que aplicações React usam para isso: fazer fetch dentro de um effect e manter 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 chamar 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 desconhecidos, o manual de JavaScript cobre tudo do zero em Async JavaScript; tudo aqui se baseia direto nisso.

useEffect é o hook que executa código depois que React renderiza um componente, em vez de durante a renderização em si. Você passa uma função para executar e um array de dependências, a lista de valores que devem fazer ele rodar de novo quando mudam, aqui só url. Profile começa com duas peças 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 sucede depois de uma anterior ter falhado nunca limparia o erro antigo, já que nada volta a colocar error em 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 resolvem normalmente como um objeto response, por isso o primeiro .then checa r.ok e lança antes da resposta chegar em .json(). Pule essa checagem e uma requisição falhada fluiria direto para data como se tivesse sucedido. Uma vez que a checagem passa, .json() lê o body e o resultado cai em data através de setData. Se qualquer coisa disso rejeita, .catch coloca o problema em error no lugar. Lá embaixo no JSX, error e data são checados como qualquer outro estado: um erro renderiza uma mensagem, nenhum dado 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 sair e algo voltar, então if (!data) return <p>Loading...</p> cobre essa lacuna sozinho. Alguns componentes adicionam um booleano isLoading dedicado para controle mais fino, mas derivar carregamento do estado que você já tem é muitas vezes mais simples, e é um valor a menos para manter sincronizado.

A variável active dentro do effect é uma flag de limpeza. useEffect pode retornar uma função, e React chama essa função logo antes do effect rodar de novo e quando o componente é desmontado. Aqui, a função retornada coloca active em false, e tanto .then quanto .catch checam ela antes de chamar setData ou setError. Sem essa checagem, uma requisição ainda em voo quando url muda, ou quando o componente sai da tela completamente, eventualmente resolveria e tentaria atualizar estado que não pertence mais ao que está sendo mostrado. A flag transforma uma resposta tardia em uma no-op em vez de um bug.

Fazer fetch à mão assim vale a pena entender, porque é o padrão no qual quase tudo mais se baseia. Em uma aplicação real você normalmente vai para uma biblioteca de data-fetching como React Query, ou o carregamento de dados integrado de um 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, retries 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 aresta 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 dispara uma nova requisição antes da anterior ter resolvido, e respostas de rede não chegam confiávelmente 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 uma url que você nem está mostrando mais. É exatamente isso 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 consegue escrever no estado da renderização atual uma vez que sua própria limpeza tiver disparado.

AbortController é a outra ferramenta para isso, e vai além: cancela a 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])

Essa versão ainda precisa das mesmas duas correções do effect acima: resetar data e error no topo da execução, para que uma tentativa novamente possa realmente limpar uma falha anterior ou um perfil obsoleto, e checar r.ok antes de fazer parse do body, já que fetch trata um 404 ou 500 como uma requisição completada em vez de uma rejeição. AbortController só 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 carrega entre execuções ou como fetch trata uma resposta HTTP falha por si só. Ambas abordagens fixam o problema de ordenação, mas abortar também economiza a rede do trabalho de terminar uma requisição que ninguém mais quer.

A direção mais nova, a partir do React 19, é ler dados async com o hook use dentro de um componente envolvido em <Suspense>, que deixa React lidar com o estado pendente e carregamento no nível do framework em vez de à mão em cada componente. use não é tipicamente usado sozinho. É normalmente 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 mais amplo de data-fetching.

JunoFetch em um effect, observe os estados O padrão para lembrar: coloque o fetch dentro de useEffect, mantenha data e error em estado, e checá-los no seu JSX para decidir o que mostrar. Carregamento é o momento em que ambos ainda estão vazios.

A flag active para uma resposta tardia de colocar estado em um componente que não está mais mostrando esses dados.

JunoFetch em um effect, observe os estados Faça fetch dentro de useEffect, armazene o resultado e qualquer erro em estado, e renderize fora deles diretamente em vez de adicionar um booleano de carregamento separado se você não precisa de um.

Retorne uma função de limpeza que flipa uma flag active, para que uma resposta obsoleta de uma url anterior ou um componente desmontado não conseguir sobrescrever estado atual.

Passando uma requisição única, vá para React Query ou o carregamento de dados do seu framework em vez de fazer esse à mão em toda parte.

JunoFetch em um effect, observe os estados Cada execução do effect possui seu próprio closure active, que é o que 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 o raw useEffect fetching como o mecanismo que vale a pena entender uma vez. Aplicações reais se apoiam em uma biblioteca de dados ou no hook use com Suspense para requisições reais, ambos construídos exatamente sobre esse padrão por baixo.

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