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:
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.
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. Próximo: Refs e o DOM, a válvula de escape para os trabalhos que precisam do nó do DOM em si.

