Skip to content

Optimizing Fetch Requests in React

Posted on:June 21, 2023

In this article, we’ll explore a simple custom hook called useFetch that addresses these issues and discuss how to enhance its performance.

Table of contents

Open Table of contents

The Basic useFetch Hook

This hook takes a URL as a parameter, performs a fetch request, and updates the state variables data, loading, and error accordingly.

const useFetch = url => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true); // Set loading to true by default

    // Fetch data
    fetch(url)
      .then(response => response.json())
      .then(result => setData(result))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
};

The issue with the above implementation arises when the URL changes rapidly or when the component unmounts before the fetch requests complete. In such cases, multiple fetch requests may be fired off simultaneously, leading to potential synchronization problems.

Consider a scenario where the URL changes five times in rapid succession. This could result in firing off five fetch requests, each taking a different amount of time to complete. Due to the variability in network speed, a fetch request initiated later may complete before an earlier one, causing data inconsistencies.

Aborting Fetch Requests

To address this issue, we need to ensure that we cancel previous fetch requests every time the useEffect hook runs. This involves using the cleanup function of useEffect to abort the fetch request. The key to achieving this is leveraging the built-in AbortController feature for fetch requests.

Here’s an updated version of the useFetch hook with fetch request cancellation.

const useFetch = url => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);

    // Create an abort controller
    const controller = new AbortController();
    const signal = controller.signal;

    // Fetch data with signal
    fetch(url, { signal })
      .then(response => response.json())
      .then(result => setData(result))
      .catch(err => setError(err))
      .finally(() => setLoading(false));

    // Cleanup function to abort fetch request
    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
};

By creating an AbortController and associating its signal with the fetch request, we can later use the controller’s abort method in the cleanup function to cancel the ongoing fetch request. This ensures that no outdated data is processed after a new fetch request has been initiated.

Conclusion

In conclusion, properly handling fetch requests in React is crucial to prevent data synchronization issues. The useFetch hook presented here demonstrates a simple yet effective way to address the problem by utilizing the AbortController feature. By incorporating this approach into your code, you can significantly enhance the performance and reliability of your fetch operations in React applications.