Automatically Canceling Fetch Requests
2 min read

Automatically Canceling Fetch Requests

React makes it relatively easy to handle async requests with the useEffect hook and JavaScript's Fetch API, but it doesn't give you a simple way to cancel those Fetch requests. For that, you need the elusive AbortController API.

The AbortController lets you cancel a single Fetch request. This is mainly necessary if a component unmounts during a request. Without this, you'll likely see the following message: Warning: Can’t perform a React state update on an unmounted component.

Let's look at an example of how you might use this inside a hook that makes requests:

function useOnUnmount(fn) {
  useEffect(() => () => fn(), [])
}

// initialParams = { url, params, options }
export default function useSendRequest(initialParams, immediate = true) {
  const [response, setResponse] = useState({
    data: null,
    isLoading: false,
  })

  // We need a new AbortController for each request
  const abortControllerRef = useRef()

  // Abort the request when the component unmounts
  useOnUnmount(() => abortControllerRef.current?.abort())

  const doRequest = (currentParams = null) => {
    const params = currentParams || initialParams

    // Instantiate a new AbortController and connect it to the request
    abortControllerRef.current = new AbortController()
    params.options.signal = abortControllerRef.current.signal

    setResponse({ data: null, isLoading: true })

    // `sendRequest` is just a wrapper around `fetch`
    sendRequest(params)
      .then((json) => {
        setResponse({ data: json, isLoading: false })
      })
      .finally(() => {
        abortControllerRef.current = null
      })
  }

  useEffect(() => {
    if (immediate) doRequest()
  }, [])

  return [response.data, response.isLoading, error, doRequest]
}

// const [data, isLoading, error, doRequest] = useSendRequest(...)

This is a big chunk of code, but it's mostly to illustrate a few important lines. useSendRequest is a barebones hook that provides data, isLoading, error, and doRequest variables to a component, so it can deterministically render based on the status data, isLoading, and error, or it can re-run a request with new parameters using doRequest.

About a dozen lines down, you'll see the first instance of AbortController code. We start by creating a ref so that we have a single AbortController instance to use for the entire request. We then tell React to call abort as soon as the component unmounts. This is handy, since it means our components will never have to worry about manually canceling requests.

Further down, inside doRequest, you'll see that each request registers a new AbortController, since we can only use each AbortController once. The important line here is params.options.signal = abortControllerRef.current.signal, which is what connects the AbortController to the request. Besides that, the only other relevant code is the finally callback which clears the unused AbortController.

All in all, it isn't too much additional logic to safely cancel requests, but it can be tricky to figure out if you haven't used AbortController before.