How to cancel a request inside useEffect()?
The problem
If a component unmounts (for example, the user left the page)
while an async request (fetch, axios, setTimeout) is still running,
it still completes, and you'd try to call setState on an already removed component.
The result: a warning in the console extra load on the network a memory leak
The solution - cancel the request in the useEffect cleanup function
Option 1. With AbortController (built into fetch)
import { useEffect, useState } from "react";
function UserProfile({ id }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// Create a cancellation controller
const controller = new AbortController();
const signal = controller.signal;
// Run the async request
async function loadUser() {
try {
const res = await fetch(`/api/users/${id}`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setUser(data);
} catch (err) {
// Ignore the error if the request was canceled
if (err.name !== "AbortError") setError(err);
}
}
loadUser();
// Return the cleanup function - cancel the request on unmount
return () => controller.abort();
}, [id]);
if (error) return <p>Error: {error.message}</p>;
if (!user) return <p>Loading...</p>;
return <p>Name: {user.name}</p>;
}What the code does:
AbortControllercreates a signal (signal) that can be passed intofetch.- When
controller.abort()is called:fetchis aborted instantly;- the promise goes into
catchwith an{ name: 'AbortError' }error; - we simply ignore it.
This is the official, standard way to cancel a fetch.
Option 2. For other async operations
If you're using something that doesn't support AbortController (for example, axios, setTimeout, WebSocket):
a) axios
useEffect(() => {
const controller = new AbortController();
axios.get('/api/users', { signal: controller.signal })
.then(res => setUser(res.data))
.catch(err => {
if (axios.isCancel(err)) return; // request canceled
console.error(err);
});
return () => controller.abort();
}, []);axios 1.4+ already supports
signalfromAbortController, older versions useCancelToken(deprecated).
b) setTimeout / setInterval
useEffect(() => {
const timeout = setTimeout(() => setCount(c => c + 1), 3000);
return () => clearTimeout(timeout);
}, []);c) WebSocket or EventSource
useEffect(() => {
const socket = new WebSocket("wss://example.com");
socket.onmessage = e => console.log(e.data);
return () => socket.close();
}, []);Option 3. If the library manages cancellation itself
Libraries like TanStack Query (React Query) or SWR already:
- cancel requests on unmount;
- do not call
setStateafter unmount; - cache results and reuse them.
Example with React Query:
const { data, isLoading } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then(r => r.json())
});Here everything is safe out of the box.
SUMMARY
| What to do | How |
|---|---|
| Create a controller | const controller = new AbortController() |
| Pass the signal into the request | { signal: controller.signal } |
Catch the error in catch | if (err.name === 'AbortError') return; |
| Clean up in the cleanup function | return () => controller.abort(); |
In short:
An async request inside
useEffect()should be canceled in the cleanup function, to prevent updating the state of an unmounted component and to avoid memory leaks.fetchusesAbortController, other APIs have their own methods (clearTimeout,socket.close(), etc.).
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.