Hint
This is the correct useEffect cleanup for fetch: return () => controller.abort(). When the component unmounts or deps change, the pending request is cancelled — preventing state updates on unmounted components.
// Simulates useEffect cleanup with AbortController
function makeFetch(signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
if (signal.aborted) {
reject(new Error('AbortError'));
} else {
resolve('data loaded');
}
}, 10);
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('AbortError'));
});
});
}
const controller = new AbortController();
let result = null;
makeFetch(controller.signal)
.then(data => { result = data; })
.catch(e => { result = 'cancelled: ' + e.message; });
// Component "unmounts" before fetch completes
controller.abort();
setTimeout(() => console.log(result), 20);cancelled: AbortError
Explanation: abort() fires before the 10ms fetch completes. The abort event listener rejects the promise. .catch captures it as 'cancelled: AbortError'.
Key Insight: This is the correct useEffect cleanup for fetch: return () => controller.abort(). When the component unmounts or deps change, the pending request is cancelled — preventing state updates on unmounted components.