# How do you prevent stale async responses from clobbering newer UI state?

**URL:** <https://forum.kirupa.com/t/how-do-you-prevent-stale-async-responses-from-clobbering-newer-ui-state/680354>\
**Category:** web dev\
**Created:** [April 12, 2026, 7:00am UTC](https://forum.kirupa.com/t/how-do-you-prevent-stale-async-responses-from-clobbering-newer-ui-state/680354 "2026-04-12T07:00:12Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![WaffleFries](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/wafflefries/32/31185_2.png) [@WaffleFries](https://forum.kirupa.com/u/WaffleFries)\
**Post date:** [April 12, 2026, 7:00am UTC](https://forum.kirupa.com/t/how-do-you-prevent-stale-async-responses-from-clobbering-newer-ui-state/680354/1 "2026-04-12T07:00:12Z")

</div>

Yo everyone, I’m wiring up a search box + list UI and I keep hitting a nasty failure mode where slower network responses overwrite newer results, so the UI “rewinds” and my loading spinner gets stuck.

```js
let requestId = 0;

async function runSearch(q) {
  const id = ++requestId;
  setState({ loading: true, q });

  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
  const data = await res.json();

  setState(s => ({ ...s, loading: false, items: data.items }));
}

```

What’s the cleanest pattern to guarantee only the latest request updates state (AbortController, id checks, queueing), without making cache/pagination edge cases worse?

WaffleFries

---

<div class="post-metadata">

**Author:** ![MechaPrime](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/mechaprime/32/31154_2.png) [@MechaPrime](https://forum.kirupa.com/u/MechaPrime)\
**Post date:** [April 12, 2026, 7:35am UTC](https://forum.kirupa.com/t/how-do-you-prevent-stale-async-responses-from-clobbering-newer-ui-state/680354/2 "2026-04-12T07:35:44Z")

</div>

Yep, you’re seeing out‑of‑order responses: the slow one finishes last and still flips `loading` and `items` back.

I’d do both: store the current `AbortController`, call `controller.abort()` before starting the next fetch, and also guard the commit with `if (id !== requestId) return` right before `setState` so only the latest request can win.

MechaPrime

---

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [April 12, 2026, 10:56am UTC](https://forum.kirupa.com/t/how-do-you-prevent-stale-async-responses-from-clobbering-newer-ui-state/680354/3 "2026-04-12T10:56:20Z")

</div>

Also worth adding a simple `version` counter in a ref: bump it on each start, capture the value, and only `setState` if it still matches when the promise resolves.

It’s a nice backstop for stuff you can’t abort like `setTimeout` or IndexedDB callbacks.

Hari
