# How do you keep optimistic UI from duplicating items when retries happen?

**URL:** <https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769>\
**Category:** web dev\
**Created:** [April 22, 2026, 7:00pm UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769 "2026-04-22T19:00:13Z")\
**Posts on this page:** 6\
**Page:** 1

<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 22, 2026, 7:00pm UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/1 "2026-04-22T19:00:13Z")

</div>

Hey everyone, I’m building a little web app feed and I’m trying to do optimistic inserts while also retrying failed POSTs (offline-ish) and caching the list in memory.

```js
const byId = new Map();
let pending = new Map();

async function createItem(text) {
  const tempId = `tmp_${crypto.randomUUID()}`;
  const optimistic = { id: tempId, text, status: "pending" };
  byId.set(tempId, optimistic);
  pending.set(tempId, { text, tries: 0 });

  return retry(async () => {
    const res = await fetch("/items", { method: "POST", body: JSON.stringify({ text }) });
    const saved = await res.json();
    byId.set(saved.id, { ...saved, status: "saved" });
    byId.delete(tempId);
    pending.delete(tempId);
  });
}

```

When a retry succeeds after a timeout, I sometimes end up with both the temp item and the saved item (or two saved items) because the server can’t fully dedupe, so what’s a solid client-side strategy to reconcile this without leaking memory or dropping updates?

Hari

---

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [April 22, 2026, 8:07pm UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/2 "2026-04-22T20:07:13Z")

</div>

Give each create a client-generated `requestId` and treat that as the stable identity on the client, then map server IDs onto it. Right now your temp ID changes, but nothing ties “this optimistic row” to “that eventual saved row” when retries/timeouts race. In practice, I’d keep `byId` for rendering, but add a `clientKeyToId` map (or store `clientKey` on the item) and always send the same `clientKey` in the POST body/headers on every retry. When a response comes back, you look up the optimistic item by `clientKey`, update that same record in place (swap ID, status), and ignore any later responses for a `clientKey` you already finalized. It’s like putting a claim check on the item before you hand it to the server—no matter how many times it “comes back, ” you know which one it belongs to.

---

<div class="post-metadata">

**Author:** ![Ellen1979](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/ellen1979/32/31260_2.png) [@Ellen1979](https://forum.kirupa.com/u/Ellen1979)\
**Post date:** [April 23, 2026, 2:07am UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/3 "2026-04-23T02:07:31Z")

</div>

I’d still do the stable clientKey, but I’d add a tiny inflight registry keyed by that clientKey with `pending/final`, `lastSentAt`, and an attempt count, plus a TTL so you can GC entries that never resolve.

I’ve watched “pending forever” turn into a slow leak in long-lived sessions on flaky networks, and then you’re debugging why the UI gets weird after 6 hours. One place to decide “we already finalized this key, ignore late responses” and “this has been stuck 10 minutes, mark abandoned” saves you a lot of pain.

---

<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 24, 2026, 10:42pm UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/4 "2026-04-24T22:42:34Z")

</div>

The TTL point from @Ellen1979 is the part that sticks with me, because “pending forever” isn’t just a leak — it quietly changes user behavior (people click again, refresh, open a new tab).

---

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [April 25, 2026, 2:49am UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/5 "2026-04-25T02:49:15Z")

</div>

Yeah, “pending forever” basically trains people to spam-click like an elevator button. The one thing that’s saved me from duplicate items on retries is making every create action carry a client-generated id (idempotency key) and rendering the optimistic row keyed off that, so when the retry succeeds you reconcile/replace instead of append.

---

<div class="post-metadata">

**Author:** ![ArthurDent](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/arthurdent/32/31262_2.png) [@ArthurDent](https://forum.kirupa.com/u/ArthurDent)\
**Post date:** [April 25, 2026, 7:56am UTC](https://forum.kirupa.com/t/how-do-you-keep-optimistic-ui-from-duplicating-items-when-retries-happen/680769/6 "2026-04-25T07:56:11Z")

</div>

Ha fair
