Definition
This is the pattern to use, on a screen that sends a request to the server on every keystroke like a search box, when you want to guarantee that "the result that ultimately remains on screen is always the result of the last term typed." Typing fast makes multiple requests overlap, and this prevents the accident where an earlier term's result mistakenly stays on screen.
It combines three tools: debounce (wait until input briefly pauses, then send just once — e.g. if you type again within 200ms, the request is deferred) to reduce the number of calls, AbortController (the browser-standard object that cancels an in-flight fetch mid-way) to cancel the previous request, and an ever-increasing "latest request id" to ignore late-arriving responses (out-of-order — where an earlier-sent request arrives later).
Why it's needed
Typing fast fires several requests at once, and network latency can make an earlier-sent request arrive later. Drawing that straight to the screen creates a race condition (where the completion order of multiple tasks gets tangled and the result goes wrong), with an old result overwriting the latest one. Also, requesting on every keystroke increases server load. This pattern handles call frequency, cancellation, and order guarantee together to build a consistent UI that always reflects only the latest query.
How it works
- Input change → set a debounce timer, clear the previous timer.
- Assign
id = ++refright before the request; on response arrival, reflect to state only whenid === ref. - On a new request / unmount, abort the previous in-flight request with AbortController.
- Derive result/status during render: compute status (idle/loading/ready/error) from whether the last resolved query == the current query, and write state only in the async callback.
Practical application
Trade-offs
The longer the debounce window, the fewer requests but the slower the response; the shorter it is, the faster the response but the more requests. The id guard alone keeps consistency, but adding AbortController further reduces unnecessary network/server cost (requires cancellation support).
When not to use it
For one-off, idempotent, order-independent requests, or when results must accumulate (like infinite-scroll append), the "reflect only the latest" rule instead discards data. In those cases, use an accumulate-merge strategy.
Common mistakes
- Handling an abort error as a general error, exposing an error UI to the user.
- Putting the request id in state, causing unnecessary renders (a ref is correct).
- Writing results with a synchronous setState in the effect body, causing duplicate renders / flicker (deriving during render is safe).
Related concepts
- serverless-stateless-execution — the execution model of the server-side search function these requests reach.