useCancelToken: a custom React hook for cancelling Axios requests
Tags: axios, react, hooks, javascript
Published: 2021-04-10 09:17:48

---
title: useCancelToken: a custom React hook for cancelling Axios requests
published: true
description: How to create a custom React hook for cancelling Axios requests.
tags: axios, react, hooks, javascript
//cover_image: https://direct_url_to_image.jpg
---

## Update

Note that as of Axios `v0.22.0`, the `cancelToken` API [is deprecated](https://axios-http.com/docs/cancellation) in favor of using a fetch-like `AbortController`.

There are already various hooks available online for `AbortController` so I won't recreate it here but at a high-level it's conceptually quite similar to the cancel token!

## What's the problem?

When developing with React and updating state inside of components, you may have come across the following error before:

`Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.`

While this error could pop up for various reasons, one common cause is attempting to update state within the callback of a network request after the component has been destroyed.

For example, imagine we have a modal (yes I know, modals are inherently problematic, but for a lot of us they're also unavoidable) that when opened makes a request for some super important data it needs to set in state and show to the user:

```js
const Modal = () => {
  const [importantData, setImportantData] = useState({});

  useEffect(() => {
    axios.get('/important_data')
      .then((response) => {
        setImportantData(response.data);
      });
  }, []);

  return (
    // JSX with important data
  )
}
```

_Note: While this post is about [Axios](https://github.com/axios/axios) specifically, the idea can be applied to other APIs, like `fetch`._


This is nice and works swimmingly when the user opens the modal and keeps it open. But what if they close it while the request is still in process? Sure the component may be gone; however, that callback within `.then()` is still hanging around waiting to get executed.

Assuming the component unmounts on close, this will cause the error noted above to occupy our console like the US occupying native land, since we'll be attempting to set our `importantData` state within a component that no longer exists.

## What can we do about it?

One solution to this issue is to ensure that whenever our component unmounts, we cancel our pending request. 

"But our request is already gone!" I hear you say.. "How can we cancel it??" you yell in despair..

Never fear fellow dev frantically trying to finish a feature before their deadline, as the folks behind Axios have already responsibly [built in cancellation functionality](https://github.com/axios/axios#cancellation)!

The idea is that we create a cancel token and send it along with our request, which allows for us to cancel said request whenever we like.

In our `Modal` component, this would look something like the following:

```js
const Modal = () => {
  const [importantData, setImportantData] = useState({});

  useEffect(() => {
    const source = axios.CancelToken.source(); 
    axios.get('/important_data', {
      cancelToken: source.token
    }).then((response) => {
      setImportantData(response.data);
    }).catch((error) => {
      if (axios.isCancel(error)) return;
    });

    return () => source.cancel();
  }, []);

  return (
    // JSX with important data
  )
}
```

Notice now we're performing a few extra steps with our lil `axios`. Before we send the request we now create a `source` variable holding the result of `axios.CancelToken.source`, which is like a reference that we can associate with our request.

Then, along with our request we send an extra piece of data, `cancelToken`, containing our source's token. 

However, this alone still doesn't accomplish our goal of cancelling on unmount! 

So, we also make sure to return a function from our `useEffect` that cancels our source's token, which [by design will run when the component unmounts](https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1).

Also note that when we cancel a token, the pending promise is rejected, resulting in an error. If you don't handle this error, it will pop up in the console. 

Conveniently, Axios also provides an `isCancel` function which allows you to determine if an error returned from a request is due to a cancellation, which you can see above in our `catch` block.

This is cool for one-off use cases, but realistically we're going to need to reuse this functionality in many components (and even many times in the same component). So let's make our own hook out of it!

## Hook, line, something something..

```js
import { useRef, useEffect, useCallback } from 'react';
import { CancelToken, isCancel } from 'axios';

/**
 * When a component unmounts, we need to cancel any potentially
 * ongoing Axios calls that result in a state update on success / fail.
 * This function sets up the appropriate useEffect to handle the canceling.
 *
 * @returns {newCancelToken: function, isCancel: function}
 * newCancelToken - used to generate the cancel token sent in the Axios request.
 * isCancel - used to check if error returned in response is a cancel token error.
 */
export const useCancelToken = () => {
  const axiosSource = useRef(null);
  const newCancelToken = useCallback(() => {
    axiosSource.current = CancelToken.source();
    return axiosSource.current.token;
  }, []);

  useEffect(
    () => () => {
      if (axiosSource.current) axiosSource.current.cancel();
    },
    []
  );

  return { newCancelToken, isCancel };
};
```

The hook, `useCancelToken`, utilizes `useRef` to store our cancel token source. This is so that our source remains the same in case of a more complex component where re-renders may occur while a request is being made.

Further, our hook sets up and exports a `newCancelToken` function, which sets the ref's current value to the created source and returns the token itself, so the consumer can send it along with their request. Note, that this function is memoized via `useCallback`, so that it can safely be added to a `useEffect` dependency array without causing an infinite loop of rendering.

I like this approach as I don't think the person using this hook should have to deal with the source object at all. All they should have to do is send the token with the request and let the hook handle the rest!

Last but not least, we set up a `useEffect` with the sole purpose of cancelling the current source's token on unmount.

Note, we also export `isCancel` so the consumer can handle their request failure errors appropriately.

So, how would we use this in our `Modal` component?

```js
import { useCancelToken } from './hooks.js';

const Modal = () => {
  const [importantData, setImportantData] = useState({});
  const { newCancelToken, isCancel } = useCancelToken();

  useEffect(() => {
    axios.get('/important_data', {
      cancelToken: newCancelToken()
    }).then((response) => {
      setImportantData(response.data);
    }).catch((error) => {
      if (isCancel(error)) return;
    });
  }, [newCancelToken, isCancel]);

  return (
    // JSX with important data
  )
}
```

Now all we do is call our `newCancelToken()` function when sending our request and check the potentially resulting error with `isCancel`. We don't even have to set up a cleanup return function!

*happy dance*

*Note that calling `newCancelToken` multiple times within the same component won't actually allow you to cancel multiple requests. For that you either need to call it once and pass the `cancelToken` to each request (also storing it in a ref if the component might re-render) or tweak the hook to return the `cancelToken` instead of the function, as [Mostafa helpfully explains in their comment](https://dev.to/mostafaomar98/comment/1lc0g).

## Conclusion

When developing it can be easy to forget about all the uncertainties and gotchas of real-world use.

Maybe the end user's network isn't as fast as the one we're developing on. Maybe they lose connectivity midway through using a feature. Maybe they didn't want to use said feature at all and navigate away from it / close it immediately. And so on and so on. 

Thus, it's important to program a bit defensively and ensure we cover our bases. Using a cancel token for async routines is one such example.

Also - I wrote `tooken` instead of `token` way too many times while writing this. Also also - tooken > taken.