React Hook - useFetch


O hook personalizado useFetch é uma implementação que simplifica a lógica associada à realização de requisições assíncronas em componentes React. Ele encapsula a complexidade do ciclo de vida de uma solicitação HTTP, proporcionando uma abordagem mais limpa e reutilizável para o gerenciamento de chamadas assíncronas.

Vamos explorar a implementação do useFetch:

1. Importação de Hooks e funções auxiliares

import { useEffect, useRef, useState } from 'react';
 
const isObjectEqual = (objA, objB) => {
  return JSON.stringify(objA) === JSON.stringify(objB);
};

O useFetch utiliza useEffect, useRef, e useState do React. Além disso, inclui uma função auxiliar isObjectEqual, que compara dois objetos para verificar se são iguais.

2. Estados internos do Hook

const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [shouldLoad, setShouldLoad] = useState(false);
const urlRef = useRef(url);
const optionsRef = useRef(options);

O useFetch mantém três estados principais:

  • result para armazenar o resultado da solicitação;
  • loading para rastrear o estado de carregamento;
  • shouldLoad para sinalizar quando uma nova solicitação deve ser iniciada.

Além disso, ele utiliza refs (urlRef e optionsRef) para armazenar a URL e as opções da solicitação, garantindo que mudanças nesses valores disparem uma nova busca.

3. Efeito para monitorar mudanças na URL e Opções

useEffect(() => {
  let changed = false;
 
  if (!isObjectEqual(url, urlRef.current)) {
    urlRef.current = url;
    changed = true;
  }
 
  if (!isObjectEqual(options, optionsRef.current)) {
    optionsRef.current = options;
    changed = true;
  }
 
  if (changed) {
    setShouldLoad((s) => !s);
  }
}, [url, options]);

Este efeito é acionado sempre que a URL ou as opções da solicitação mudam. Ele compara os valores atuais com os armazenados nas refs e, se houver uma diferença, atualiza urlRef e/ou optionsRef, sinalizando que uma nova solicitação deve ser iniciada.

4. Efeito principal para realizar a requisição

useEffect(() => {
  let wait = false;
  const controller = new AbortController();
  const signal = controller.signal;
 
  setLoading(true);
 
  const fetchData = async () => {
    await new Promise((r) => setTimeout(r, 1000));
 
    try {
      const response = await fetch(urlRef.current, { signal, ...optionsRef.current });
      const jsonResult = await response.json();
 
      if (!wait) {
        setResult(jsonResult);
        setLoading(false);
      }
    } catch (e) {
      if (!wait) {
        setLoading(false);
      }
      console.log(e.message);
    }
  };
 
  fetchData();
 
  return () => {
    wait = true;
    controller.abort();
  };
}, [shouldLoad]);

Neste segundo efeito, a lógica da solicitação é tratada. O código utiliza um AbortController para garantir que a solicitação seja interrompida se o componente for desmontado antes que a solicitação seja concluída. O estado de loading é ativado, e após um pequeno atraso simulado de 1 segundo (para ilustrar o carregamento), a solicitação real é feita usando fetch. O resultado é armazenado em result, e loading é desativado, a menos que a solicitação seja cancelada (wait é verdadeiro).

5. Retorno do Hook

return [result, loading];

O useFetch retorna um array contendo o resultado da solicitação (result) e um indicador de carregamento (loading). Este é o resultado final que pode ser utilizado no componente React que utiliza este gancho.

6. Código

import { useEffect, useRef, useState } from 'react';
 
const isObjectEqual = (objA, objB) => {
  return JSON.stringify(objA) === JSON.stringify(objB);
};
 
export const useFetch = (url, options) => {
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [shouldLoad, setShouldLoad] = useState(false);
  const urlRef = useRef(url);
  const optionsRef = useRef(options);
 
  useEffect(() => {
    let changed = false;
 
    if (!isObjectEqual(url, urlRef.current)) {
      urlRef.current = url;
      changed = true;
    }
 
    if (!isObjectEqual(options, optionsRef.current)) {
      optionsRef.current = options;
      changed = true;
    }
 
    if (changed) {
      setShouldLoad((s) => !s);
    }
  }, [url, options]);
 
  useEffect(() => {
    let wait = false;
    const controller = new AbortController();
    const signal = controller.signal;
 
    setLoading(true);
 
    const fetchData = async () => {
      await new Promise((r) => setTimeout(r, 1000));
 
      try {
        const response = await fetch(urlRef.current, { signal, ...optionsRef.current });
        const jsonResult = await response.json();
 
        if (!wait) {
          setResult(jsonResult);
          setLoading(false);
        }
      } catch (e) {
        if (!wait) {
          setLoading(false);
        }
        console.log(e.message);
      }
    };
 
    fetchData();
 
    return () => {
      wait = true;
      controller.abort();
    };
  }, [shouldLoad]);
 
  return [result, loading];
};

Referências