0 day streak
javascript / intermediate
Snippet

Cancelling In-Flight HTTP Requests on Component Unmount with AbortController

Instantiating an `AbortController` inside `useEffect` connects the DOM lifecycle to ongoing asynchronous `fetch` calls. Returning `controller.abort()` in the cleanup function aborts in-flight network promises when the query changes or the component unmounts, while checking `err.name !== 'AbortError'` suppresses intentional cancellations.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function SearchResults({ query }) {
const [results, setResults] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
async function performSearch() {
setIsLoading(true);
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal });
const data = await response.json();
setResults(data);
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Fetch operation failed:', err);
}
} finally {
setIsLoading(false);
}
}
if (query) performSearch();
return () => {
controller.abort();
};
}, [query]);
return (
<div>
{isLoading && <span>Searching...</span>}
<ul>{results.map((r) => <li key={r.id}>{r.title}</li>)}</ul>
</div>
);
}
react
Breakdown
1
const controller = new AbortController();
Creates a cancellation signal provider specifically for the current effect execution lifecycle.
2
const response = await fetch(..., { signal });
Passes the abort signal to the fetch API to support hardware and network-level termination.
3
if (err.name !== 'AbortError') {
Filters out expected abort exceptions to prevent treating intentional unmounts as real errors.