React Curriculum

Effect Cleanups & Race Conditions

Hard100% Free~30 mins#react#useeffect#race-conditions#abortcontroller#async#cleanup
Interview Answer (30-Second Summary)

Race conditions occur when asynchronous requests resolve out of order (e.g., Query A sent before Query B, but B finishes before A). In useEffect, race conditions are solved using an active/ignore boolean flag or by canceling the in-flight request via AbortController in the cleanup function.

Key Learning Objectives
✓

Diagnose network race conditions in React search and pagination components.

✓

Implement the boolean flag cleanup pattern (ignore flag) to discard stale async responses.

✓

Implement network request cancellation using AbortController and abortSignal.

✓

Explain why updating state on an unmounted component was flagged in older React versions.

The Interview Problem & Context

Implement an asynchronous search fetch hook in useEffect that cancels or ignores outdated in-flight requests when the search query changes rapidly.

1import { useState, useEffect } from 'react';
2
3export function useSearchResults(query: string) {
4 const [results, setResults] = useState<string[]>([]);
5 const [loading, setLoading] = useState(false);
6
7 useEffect(() => {
8 // Edge case: empty query
9 if (!query) {
10 setResults([]);
11 return;
12 }
13
14 // 1. Create AbortController to cancel previous network request
15 const controller = new AbortController();
16 let isIgnored = false;
17
18 async function fetchResults() {
19 setLoading(true);
20 try {
21 const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
22 signal: controller.signal,
23 });
24 const data = await response.json();
25
26 // 2. Prevent setting state if query changed during flight!
27 if (!isIgnored) {
28 setResults(data);
29 }
30 } catch (err: any) {
31 if (err.name !== 'AbortError' && !isIgnored) {
32 console.error('Fetch error:', err);
33 }
34 } finally {
35 if (!isIgnored) {
36 setLoading(false);
37 }
38 }
39 }
40
41 fetchResults();
42
43 // 3. Cleanup: executes when query changes or component unmounts
44 return () => {
45 isIgnored = true;
46 controller.abort(); // Cancels the in-flight HTTP request!
47 };
48 }, [query]);
49
50 return { results, loading };
51}
Deep Architectural Explanation

Anatomy of a Network Race Condition

Consider a search input:

  1. User types "c" -> Request 1 dispatched (takes 1000ms).
  2. User types "ca" -> Request 2 dispatched (takes 200ms).
  3. Request 2 finishes at 200ms: UI shows results for "ca".
  4. Request 1 finishes at 1000ms: Overwrites state with results for "c"!
    The user is left looking at results for "c" even though the input box says "ca".

The Two Architectural Solutions

  1. The Boolean Ignore Flag Pattern:
tsx
useEffect(() => {
  let ignore = false;
  asyncFetch(id).then(res => {
    if (!ignore) setData(res);
  });
  return () => { ignore = true; };
}, [id]);
  • Simple and bulletproof against state corruption.
  • However, the HTTP request still downloads data in the background over the wire.
  1. The AbortController Pattern:
tsx
const controller = new AbortController();
fetch(url, { signal: controller.signal });
return () => controller.abort();
  • Cancels the HTTP request immediately at the browser network layer, saving bandwidth and mobile battery life.
  • Remember to catch and ignore err.name === 'AbortError'.
Common Interview Traps & Pitfalls
✕

Not catching AbortError, leading to uncaught promise rejection errors in console.

✕

Setting state on unmounted components without an active/ignore flag.

✕

Reusing the same AbortController instance across multiple renders instead of instantiating a new one per effect execution.

Senior Interviewer Follow-Up Probes
?

Why is dedicated server-state caching (TanStack Query / SWR) preferred over raw useEffect fetching in production?

?

How does React 19 use() API handle promise cancellation and caching under Suspense?

?

What happens if an aborted fetch request hits a Service Worker cache?

Frequently Asked Interview Questions
What is a race condition in React async data fetching?

A race condition occurs when asynchronous responses resolve in a different order than they were requested, causing older, stale data to overwrite newer data in state.

When dependencies change, the cleanup function runs and sets ignore = true. When the older asynchronous promise resolves, it checks the flag and discards the result.

AbortController stops the HTTP request over the network, freeing up browser socket connections and saving network bandwidth, whereas a boolean flag still downloads the response.

Wrap the fetch in a try/catch block and check if (error.name === "AbortError"). If true, ignore the error because it was intentionally cancelled.

The React team removed that warning in React 18 because setting state on unmounted components does not cause a memory leak in JavaScript, though handling race conditions remains essential for correctness.