javascript / intermediate
Snippet
Canceling In-Flight Fetch Requests with AbortController in useEffect
When components unmount or props change before an asynchronous request completes, setting state can cause memory leaks or race conditions. Instantiating an AbortController within useEffect and passing its signal to fetch allows clean cancellation on cleanup, while filtering out 'AbortError' prevents unwanted UI error states.
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
import { useState, useEffect } from 'react';export function UserProfile({ userId }) {const [user, setUser] = useState(null);const [fetchError, setFetchError] = useState(null);useEffect(() => {const controller = new AbortController();const { signal } = controller;async function loadUserData() {try {setFetchError(null);const res = await fetch(`/api/users/${userId}`, { signal });if (!res.ok) throw new Error(`HTTP error: ${res.status}`);const data = await res.json();setUser(data);} catch (err) {if (err.name !== 'AbortError') {setFetchError(err.message);}}}loadUserData();return () => controller.abort();}, [userId]);if (fetchError) return <p role="alert">Error: {fetchError}</p>;if (!user) return <p>Loading...</p>;return <div><h1>{user.name}</h1><p>{user.email}</p></div>;}
react
Breakdown
1
const controller = new AbortController();
Creates a new AbortController instance scoped to the current effect lifecycle.
2
const res = await fetch(`/api/users/${userId}`, { signal });
Passes the controller's abort signal to the fetch API to track network lifecycle.
3
if (err.name !== 'AbortError') {
Ignores intentional cancellation errors so they do not populate the UI error state.
4
return () => controller.abort();
Aborts pending network requests immediately when the component unmounts or userId changes.