Build awareness and adoption for your software startup with Circuit.

How to Cancel Fetch and Axios Requests in React’s useEffect Hook

Some practical ways to cancel your network request in React with Fetch and Axios

Photo by Albert Stoynov on Unsplash

The Fetch API and Axios are two of the most popular HTTP clients used to make network and API calls in modern web applications. However, when using these tools in React, efficiency can become a concern.

It’s a good practice in React to write components assuming they could unmount at any time and to clean up any side effects (like network requests, event listeners, or subscriptions) in the cleanup function of the useEffect hook. This helps prevent memory leaks and unexpected behaviours.

This article aims to show you some practical ways to cancel your network request in the React useEffect hook and generally accepted strategies for doing this while improving the overall quality of your React code. Before we delve into it, here is what the UI for this example looks like:

Dan Abramov Example on canceling request with Axios

The examples in this tutorial were inspired by Dan Abramov’s work on Canceling Requests With useEffect and Axios. Feel free to open the CodeSandbox environment and use these examples to observe the results for yourself.

In the example project, you can view the GitHub profiles of some interesting individuals in the React community when you switch between them by clicking on the button that has their GitHub username.

Making network requests without cleanup functions

The use of these powerful libraries shouldn’t be the problem; they are meant to enhance your work, not hinder it. However, when used incorrectly in React, certain issues may arise. The useEffect hook is one of the most common places in React to write code that interacts with external systems because it allows you to control how and when these effects are executed.

Components of a useEffect hook:

Image by author: Components of a useEffect hook:

Here are some common mistakes that are often made when using these libraries with the useEffect hook.

Using Fetch API without a cleanup

Here is what most code you will find on the internet and in many blog posts does with the Fetch API:

useEffect(() => {
  setLoading(true);
  (async () => {
    try {
      const response = await fetch(`https://api.github.com/users/${username}`);
      if (response.ok) {
        const data = await response.json();
        setProfile(data);
        setLoading(false);
      } else {
        console.error(`HTTP error! Status: ${response.status}`);
      }
    } catch (error) {
      console.error(error);
    }
  })();
}, [username]);

This code takes a value from a state variable, username, and uses it to call the GitHub Users API in order to retrieve a user’s profile. If the response is successful (HTTP status code 200), it sets the response data in a state variable called profile using setProfile(). If any errors occur during the process, they are logged to the console.

Using Axios without a cleanup

Here is a similar code using Axios that accomplishes the same functionality:

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

    try {
      const response = await axios.get(
        `https://api.github.com/users/${username}`
      );

      if (response.status === 200) {
        setProfile(response.data);
        setLoading(false);
      } else {
        console.error(`HTTP error! Status: ${response.status}`);
      }
    } catch (error) {
      console.error(error);
    }
  })();
}, [username]);

What might go wrong?

This code works just fine; it retrieves the user’s profile if the username is available on GitHub. But what could possibly go wrong?

If we try to run the examples above without any clean up function that can cancel requests within the useEffect hook, observe what can happen, as depicted in this GIF.

Results of making network requests without cleanup functions

To provide some context, I’ve opened my browser’s network debugging tab alongside the application. When I rapidly switch between users, take note of the results. Subsequent requests are not cancelled when the component updates with a new UI, and since this is an asynchronous function, they can execute concurrently and at unpredictable times. This leads to a situation where the browser batches all the requests, and as they become ready, they overwrite each other.

This behaviour is inconsistent and clearly not in line with our intentions. It highlights one of the issues at hand. Now, if a similar code is deployed in a production environment dealing with a substantial amount of data, it can become exceedingly challenging to pinpoint the source of potential errors.

These issues highlight the importance of implementing cleanup functions when making network requests in React.

Let’s explore an example that includes a cleanup function capable of cancelling network requests for both Fetch and Axios clients.

Making requests with cleanup functions

Here is how we can improve the code above.

Using the Fetch API with cleanup functions

With the Fetch API, you can use the AbortController to cancel the request when the component unmounts. Here’s how you can modify your code to include an AbortController:

useEffect(() => {
  const abortController = new AbortController();
  const signal = abortController.signal;

  setLoading(true);

  (async () => {
    try {
      const response = await fetch(
        `https://api.github.com/users/${username}`,
        { signal } // Link the signal
      );

      if (!signal.aborted) {
        if (response.ok) {
          const data = await response.json();
          setProfile(data);
          setLoading(false);
        } else {
          console.error(`HTTP error! Status: ${response.status}`);
        }
      }
    } catch (error) {
      if (!signal.aborted) {
        console.error(error);
      }
    }
  })();

  return () => {
    // Cancel the request when the component unmounts
    abortController.abort();
  };
}, [username]);

In this modified code, an AbortController is employed to create an abort signal, which is then passed as an option to the fetch request. When the component unmounts, the cleanup function, provided by useEffect, aborts the ongoing fetch request by calling abortController.abort(), preventing any further state updates after the component’s unmount.

Using Axios with cleanup functions

To terminate an Axios call you can use the following methods:

  1. signal
  2. cancelToken (deprecated)

Here is how to achieve the same result using Axios

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  setLoading(true);

  (async () => {
    try {
      const response = await axios.get(
        `https://api.github.com/users/${username}`,
        { signal } // Method 1
      );

      if (response.status === 200) {
        setProfile(response.data);
        setLoading(false);
      } else {
        console.error(`HTTP error! Status: ${response.status}`);
      }
    } catch (error) {
      if (!axios.isCancel(error)) {
        console.error(error);
      }
    }
  })();

  return () => {
    // Cancel the request when the component unmounts
    controller.abort();
  };
}, [username]);

To cancel an Axios request, we create a new AbortController object and use its signal property to link it to the Axios GET request.

We can do this because the Axios HTTP client package also supports the AbortController API to cancel requests in a fetch API way. The CancelToken API of Axios is based on the withdrawn cancelable promises proposal and is deprecated since version 0.22.0.

Here is another, older way of accomplishing the same task:

useEffect(() => {
  const source = axios.CancelToken.source();

  setLoading(true);

  (async () => {
    try {
      const response = await axios.get(
        `https://api.github.com/users/${username}`,
        { cancelToken: source.token } // Method 2
      );

      if (response.status === 200) {
        setProfile(response.data);
        setLoading(false);
      } else {
        console.error(`HTTP error! Status: ${response.status}`);
      }
    } catch (error) {
      if (!axios.isCancel(error)) {
        console.error(error);
      }
    }
  })();

  return () => {
    // Cancel the request when the component unmounts
    source.cancel();
  };
}, [username]);

We create a CancelToken source with axios.CancelToken.source(), generating a token for cancelling the Axios request. Within the useEffect, we apply the cancelToken option to the Axios GET request, linking it to the generated token. In the cleanup function provided by useEffect, we use source.cancel() to terminate the Axios request when the component unmounts, preventing unnecessary state updates.

This code achieves the same functionality as the Fetch example but with Axios and request cancellation using an AbortController equivalent (axios.CancelToken).

Here is the final result.

Final results of making cancellable network requests with cleanup functions

Now you can see how our application has been improved; the browser only handles the last request and cancels any previous requests that were pending before the current one.

Browser support

The AbortController interface is supported by all modern browsers and other HTTP clients such as Fetch or node-fetch.

Other best practices

In addition to cancelling requests, there are several other best practices you can employ to enhance your React application’s performance and maintainability.

  1. Caching: Implementing caching mechanisms, whether at the application or server level, can significantly boost performance. By storing previously fetched data locally, you can reduce the need for redundant network requests and provide a smoother user experience. Libraries like SWR and React Query can help you manage caching efficiently.
  2. Error Handling: Proper error handling is crucial to ensure that your application gracefully handles unexpected scenarios. Consider implementing a global error boundary or error handling components to capture and log errors consistently. Tools like Sentry, LogRocket and others can help you track and diagnose errors in production.

By keeping these practices in mind, you can ensure that your React application not only performs well but also reaches a broader audience without compatibility issues.

Conclusion:

To conclude, mastering the art of cancelling Fetch and Axios requests within React’s useEffect hook is a crucial skill for every React developer. As we’ve seen, overlooking this aspect can lead to unintended consequences, such as memory leaks and erratic behaviour in your applications.

Throughout this article, we’ve delved into the importance of cleaning up side effects, explored practical examples, and learned about tools like the AbortController and axios.CancelToken that can help us manage and cancel requests effectively.

Key Takeaways:

  1. Cleanup Matters: Always consider the cleanup of side effects when making network requests in React components. The useEffect hook provides an excellent place to manage these effects.
  2. AbortController (Fetch & Axios) and axios.CancelToken (Axios): These tools are your allies in cancelling requests gracefully when components unmount or when you need to abort ongoing requests.
  3. Caching and Error Handling: Beyond request cancellation, implement caching strategies and robust error handling to further enhance your application’s reliability and performance.

If you are interested in digging deeper into this topic, do well to check out some of the resources I have collected together to help you in your journey. Don’t forget, practical experience is the best teacher, and by implementing these techniques, you’ll gain a deeper understanding of how to build efficient and reliable React applications.

If you’ve enjoyed this article, following me on Medium will keep you updated on future articles covering similar topics. Additionally, you can follow me on Twitter at @ttebify for motivation and access to valuable resources. I look forward to connecting with you!

Relevant References:

  1. Understanding the useEffect cleanup function in React.
  2. How to cancel an asynchronous task in JavaScript.
  3. A complete guide to the useEffect React Hook.
  4. Why you should cancel your API calls in React.
  5. Fetching data with fetch and aborting requests.



Continue Learning