Q&A
How to easily ignore useEffect HTTP calls with ReactJS
Overview
useEffect is where data fetching commonly lives, but effects can run multiple times (dependency changes, Strict Mode double-invoke) and components can unmount mid-request. If an earlier request resolves after a newer one (or after unmount), you can render stale data or update a gone component. The remedy is the effect's cleanup function, which lets you ignore or abort the obsolete call.
graph TD
A[useEffect runs: start fetch] --> B[Deps change / unmount]
B --> C[Cleanup runs FIRST]
C --> D[Set ignore=true OR abort the request]
D --> E[Stale response is discarded]
A --> F[New effect run starts a fresh fetch]
style D fill:#1e1b4b,color:#fff
The Problem
useEffect(() => {
fetch(`/api/items/${id}`)
.then(res => res.json())
.then(data => setItems(data)); // ⚠ might set stale data or update after unmount
}, [id]);
If id changes quickly, two requests race; whichever resolves last "wins," which may be the older one. And if the component unmounts before the response, you update a non-existent component.
Solution 1: The ignore flag (simplest)
The cleanup function sets a flag that the resolved promise checks before updating state:
useEffect(() => {
let ignore = false; // closure flag for THIS effect run
fetch(`/api/items/${id}`)
.then(res => res.json())
.then(data => {
if (!ignore) setItems(data); // only apply if still current
});
return () => { ignore = true; }; // cleanup: mark this run stale
}, [id]);
On each re-run or unmount, React calls the previous run's cleanup first, flipping its ignore to true, so its late response is discarded. This is the pattern React's own docs recommend.
Solution 2: AbortController (actually cancels the request)
AbortController not only ignores but cancels the in-flight HTTP request, saving bandwidth:
useEffect(() => {
const controller = new AbortController();
fetch(`/api/items/${id}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setItems(data))
.catch(err => {
if (err.name !== "AbortError") throw err; // ignore intentional aborts
});
return () => controller.abort(); // cleanup: cancel the request
}, [id]);
Use this when you want to stop wasted network work, not just ignore the result.
With async/await
You can't make the effect callback itself async (it would return a promise, not a cleanup), so define an inner function:
useEffect(() => {
let ignore = false;
(async () => {
const res = await fetch(`/api/items/${id}`);
const data = await res.json();
if (!ignore) setItems(data);
})();
return () => { ignore = true; };
}, [id]);
React Strict Mode
In development, React 18 Strict Mode intentionally runs effects twice to surface missing cleanup. Proper cleanup (ignore flag or abort) makes your effect resilient to this double-invoke - which is exactly the behavior you want in production for rapid dependency changes too.
The Bigger-Picture Answer: Data-Fetching Libraries
For real apps, libraries like React Query (TanStack Query) or SWR handle all of this - request deduplication, cancellation, caching, stale handling, retries - so you rarely hand-roll fetch-in-useEffect. Mentioning them shows you know the production-grade solution.
Why This Matters in Interviews
React interviews use this to test understanding of effect lifecycle and race conditions. Key points: effects re-run and components unmount, so late responses can be stale; the cleanup function is the fix; the ignore flag pattern discards stale results, while AbortController cancels the request; you can't make the effect callback async directly; and Strict Mode double-invokes effects to expose missing cleanup. Recommending React Query/SWR for production is a maturity signal.
Common Pitfalls
- No cleanup → stale responses overwrite fresh data (race condition) or "update on unmounted component."
- Making the
useEffectcallbackasync(returns a promise, breaking cleanup). - Forgetting to swallow
AbortErrorafter aborting (it rejects the fetch). - Hand-rolling fetching everywhere instead of a caching library for non-trivial apps.
References
- React, "You Might Not Need an Effect" & fetching with cleanup: https://react.dev/reference/react/useEffect#fetching-data-with-effects
- MDN
AbortController: https://developer.mozilla.org/en-US/docs/Web/API/AbortController