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.