Skip to content
AITroveRead. Build. Understand.
Make this comfortable

React Effects and Request Races

Last updated: 4 Oct 20266 min read
tutorial
IntermediateBy AITrove Editorial

An effect connects a rendered component to an external system such as a network request, subscription, or browser event. When its dependencies change, the previous connection must be cleaned up before the next one takes ownership. A fetch started for one case can finish after the user opens another case. If its result is applied unconditionally, the page may show the wrong record under the new URL. An AbortController can stop a request that is no longer needed, while an identity or generation check prevents an already-completed callback from committing stale data.

Working case

Reviewer 29 opens case 47, then immediately selects case 62. The request for case 62 finishes first and paints its details. The slower case 47 response arrives later and overwrites the panel while the URL still says 62. The bug appears only on variable networks, so local testing with cached responses misses it. Tie the effect to caseId, abort the prior request during cleanup, and test both completion orders. The server still must authorize both requests; cancellation is a UI consistency tool, not access control.

Implementation boundary

javascript
function useCaseDetails(caseId) {
  const [details, setDetails] = React.useState(null);
  React.useEffect(() => {
    const controller = new AbortController();
    setDetails(null);
    fetch(`/api/cases/${caseId}`, { signal: controller.signal })
      .then(response => { if (!response.ok) throw new Error("Case load failed"); return response.json(); })
      .then(result => { if (!controller.signal.aborted) setDetails(result); })
      .catch(error => { if (!controller.signal.aborted && error.name !== "AbortError") setDetails({ error: true }); });
    return () => controller.abort();
  }, [caseId]);
  return details;
}

The sketch resets the visible state when the case changes and aborts the previous request. A real hook should validate the response, avoid updating an obsolete state from any error branch, and expose loading, not-found, permission, and retry states separately. Route-level data loading or a maintained request cache may handle deduplication, server rendering, and stale responses more efficiently than hand-written effects. If an effect subscribes to live events, cleanup must unsubscribe as well. Keep dependency lists honest rather than suppressing them to avoid reruns.

Cost and boundaries

Unneeded requests consume bandwidth and server work even if their responses are ignored; cancellation can reduce that cost but may arrive after the server has already done its work. Fetching from a mounted child can create a parent-then-child waterfall and delay useful content. Route loaders or prefetching can move data work earlier. Each custom effect adds branches for response order, abort, error, and unmount, so use it where the external connection truly belongs to the component. Measure route latency before adding another cache layer.

Failure trace

A developer removes caseId from the effect dependencies to stop duplicate loading. The hook now keeps showing case 47 when the route changes to 62. Another version keeps caseId in dependencies but never cleans up, so the old request wins a race. Both fixes treat the effect as a one-time callback instead of a connection tied to current input. Restore correct dependencies and cleanup, then simulate slow and fast responses in both orders.

Verification

  • Switch 47 to 62 with the first request deliberately slower, then reverse completion order.
  • Unmount during a request and verify cleanup prevents a stale update.
  • Force old and current requests to fail separately and inspect the visible error owner.

Practice drill

Use a test transport that resolves case 62 before case 47. Confirm that the panel and URL agree after both promises settle. Repeat with a rejected old request and verify its catch handler cannot replace the new case's state. Leave the route before either request finishes and confirm no stale update occurs. If the route framework already loads data, compare its behavior and remove the custom effect when it duplicates that mechanism.

Decision note

Prefer route or framework data loading when it already solves cache and ordering. For a local effect, make setup and cleanup symmetrical and guard every completion path.

Common Mistakes

  • Suppressing a dependency to hide rerenders.
  • Guarding success but not the error callback against stale work.
  • Treating AbortController as a server-side authorization control.

Connected lessons

Typed Frontend and Component Platform; TypeScript and Runtime API Boundaries; Web Components and Shadow Boundaries; Module Splitting and Interaction Budget; Browser Routing and History State; Fetch requests: separate HTTP failure, transport failure, and cancellation; Browser Journey and Fault-Injection Tests.

Apply and check

Build Project: typed review console and review Web Development: platform and trust contracts quiz.

Further connections

Workers, Messages, Transfer, and Cancellation; Browser Memory and Resource Lifecycles.

Further connections

Hydration and Deterministic First Render; Asynchronous Field Validation Races.

Further connections

React State and Rendering Boundaries; React Derived State and Event Ownership.

web-tech
web-development
Storage details