Hint
This is the canonical useEffect cleanup pattern for async data fetching. Return a cleanup that sets `cancelled = true` to ignore stale responses when the component re-renders or unmounts.
function fetchWithCancel(id, delay) {
let cancelled = false;
const promise = new Promise(resolve =>
setTimeout(() => resolve({ id, data: result-${id} }), delay)
);
return {
result: promise.then(r => cancelled ? null : r),
cancel: () => { cancelled = true; }
};
}
let activeResult = null;
// Simulate rapid userId changes (like fast typing)
let cleanup = null;
[1, 2, 3].forEach((id, i) => {
setTimeout(() => {
if (cleanup) cleanup(); // cancel previous
const { result, cancel } = fetchWithCancel(id, 20);
cleanup = cancel;
result.then(r => {
if (r) {
activeResult = r.data;
}
});
}, i * 5); // each request starts 5ms apart, takes 20ms
});
setTimeout(() => console.log(activeResult), 100);result-3
Explanation: Request 1 starts at 0ms, request 2 at 5ms (cancels 1), request 3 at 10ms (cancels 2). Only request 3 is not cancelled. It resolves after 30ms with 'result-3'.
Key Insight: This is the canonical useEffect cleanup pattern for async data fetching. Return a cleanup that sets cancelled = true to ignore stale responses when the component re-renders or unmounts.