DEV Community

Alvaro Guimarães
Alvaro Guimarães

Posted on

React na Prática: Lidando com requisições HTTP

Lidando com requisições HTTP

Essa é uma abordagem comum, e você já deve ter visto muitos exemplos de código que fazem chamadas HTTP dentro de um componente, variando detalhes, como o uso de fetch ou axios, ou a forma como o estado é gerenciado.

Você já deve ter visto exemplos de como refatorar esse código para um hook customizado, mas vamos fazer isso de novo.

Esse componente é relativamente simples, você tem 3 estados dentro do componente para representar o estado da
requisição.

O useEffect é executado apenas uma vez, quando o componente é montado, e é a engrenagem principal para fazer a
requisição toda funcionar.

Primeira abordagem:

import { useEffect, useState } from "react";

function App() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<any>(null);
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const response = await fetch("https://fakestoreapi.com/products");

      if (!response.ok) throw new Error("Failed to fetch data");

      const data = await response.json();

      setData(data);
      setIsLoading(false);
    };

    fetchData().catch((e) => setError(e));
  }, []);

  return (
    <div>
      {isLoading && <p>Loading...</p>}

      {error && <p>{error.message}</p>}

      {data && <pre>{JSON.stringify(data)}</pre>}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Separando a lógica de fetch em um hook

Segunda Abordagem

Nessa abordagem, isolamos a lógica de fetch em um hook customizado, que é reutilizável em qualquer componente,
mas também diminuímos a responsabilidade do componente, que agora só precisa lidar com a renderização do estado e a
requisição.

Mas isso ainda pode melhorar, o componente APP ainda precisa lidar com a lógica de fazer a requisição, apesar de não
precisar lidar com o estado da requisição.

Segunda abordagem:


// useFetch.tsx
import { useEffect, useState } from "react";

type useFetchParams<T> = {
  fetcher: () => Promise<T>;
  queryKey: string[];
};

export function useFetch<T>({ fetcher, queryKey }: useFetchParams<T>) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<any>(null);
  const [data, setData] = useState<T | null>(null);

  const queryKeyString = JSON.stringify(queryKey);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const data = await fetcher();
      setData(data);

      setIsLoading(false);
    };

    fetchData().catch((e) => setError(e));
  }, [queryKeyString]);

  return { isLoading, error, data };
}
Enter fullscreen mode Exit fullscreen mode
// App.tsx
import { useFetch } from "./useFetch.tsx";

const ENDPOINT = "https://fakestoreapi.com/products";

function App() {
  const { isLoading, error, data } = useFetch({
    queryKey: [ENDPOINT],
    fetcher: async () => {
      const response = await fetch(ENDPOINT);
      if (!response.ok) throw new Error("Failed to fetch data");
      return response.json();
    },
  })

  return (
    <div>
      {isLoading && <p>Loading...</p>}

      {error && <p>{error.message}</p>}

      {data && <pre>{JSON.stringify(data)}</pre>}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Separando a Requisição em um Hook Customizado

Agora, é o que eu considero ideal como separação de responsabilidades.

  • O Hook customizado useFetch é responsável por lidar com a lógica de fazer a requisição;
  • O hook useProductFindAll é responsável por fazer a requisição de produtos;
  • O componente App é responsável por lidar com a renderização dos produtos e o estado da requisição.

Terceira Abordagem

// useProductFindAll.tsx
import { useFetch } from "./useFetch.tsx";


const ENDPOINT = "https://fakestoreapi.com/products";


async function fetchProductFindAll(params = {}) {
  const searchParams = new URLSearchParams(params);

  const response = await fetch(ENDPOINT + `?${searchParams.toString()}`);
  if (!response.ok) throw new Error("Failed to fetch data");
  return response.json();
}

export function useProductFindAll(params = {}) {
  return useFetch({
    queryKey: [ENDPOINT, params],
    fetcher: () => fetchProductFindAll(params)
  });
}
Enter fullscreen mode Exit fullscreen mode
// App.tsx
import { useProductFindAll } from "./useProductFindAll.tsx";

function App() {
  const { isLoading, error, data } = useProductFindAll({ limit: 6 })

  return (
    <div>
      {isLoading && <p>Loading...</p>}

      {error && <p>{error.message}</p>}

      {data && <pre>{JSON.stringify(data)}</pre>}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Se você tem experiência ou conhece a biblioteca react-query, você pode perceber que o hook useFetch é muito
parecido
com o hook useQuery do react-query, e é verdade.

O react-query é uma biblioteca que abstrai a lógica de fazer requisições, e é uma ótima alternativa para lidar com
estado "back-end" no front-end. A biblioteca é muito poderosa e tem muitos recursos, como cache, refetch, paginação.

Por isso, se você está lidando com muitas requisições no seu projeto, eu recomendo que você dê uma olhada na biblioteca
react-query.

Top comments (0)