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];
};