Every uncancelled fetch is a future race condition. AbortController is the standard, free, no-library-needed way to cancel an in-flight request when the user changes their mind.
The race condition
A user types in a search box. Each keystroke fires a fetch. The fetches return out of order — the response for 'r' arrives after the response for 're'. Without cancellation, the older response wins (because it lands later in time), and the UI shows results for the wrong query. The fix is to abort superseded requests.
The API
Create a controller, pass its signal to fetch, call controller.abort() when you want to cancel. The fetch promise rejects with an AbortError. Filter that error type out of your error handling (it's expected, not exceptional).
Where to integrate
- useEffect — create the controller at the top, abort in the cleanup function (returns from the effect).
- Custom hook — same pattern, just inside the hook.
- Event handlers — store the controller in a ref so subsequent handler calls can abort the previous one.
Timeouts
For a hard timeout, combine AbortController with AbortSignal.timeout(ms): fetch(url, { signal: AbortSignal.timeout(5000) }) aborts after 5 seconds. For manual + timeout, use AbortSignal.any([ctrl.signal, AbortSignal.timeout(5000)]) — abort if either fires.