DEV Community

Alvaro Guimarães
Alvaro Guimarães

Posted on

3 2 2 2 3

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video