# What’s a clean way to handle optimistic updates without mutating my normalized cache?

**URL:** <https://forum.kirupa.com/t/what-s-a-clean-way-to-handle-optimistic-updates-without-mutating-my-normalized-cache/682115>\
**Category:** web dev\
**Created:** [June 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/what-s-a-clean-way-to-handle-optimistic-updates-without-mutating-my-normalized-cache/682115 "2026-06-16T07:00:17Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Quelly](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/quelly/32/31386_2.png) [@Quelly](https://forum.kirupa.com/u/Quelly)\
**Post date:** [June 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/what-s-a-clean-way-to-handle-optimistic-updates-without-mutating-my-normalized-cache/682115/1 "2026-06-16T07:00:17Z")

</div>

What’s up everyone? I’m wiring up a little UI state store and I’m trying to keep a normalized entity map, but my optimistic updates keep leaking mutations into other views (and then rollback gets weird).

```ts
type Entity = { id: string; name: string; version: number };

type State = {
  entities: Record<string, Entity>;
  lists: Record<string, string[]>; // listId -> entity ids
};

function optimisticRename(state: State, id: string, name: string): State {
  // I know this is sketchy, but it’s fast:
  state.entities[id].name = name;
  return state;
}

```

Given that I also need retries/rollback and don’t want to deep-clone the whole state every time, what mutation strategy or data structure pattern do you actually use here to keep updates isolated and predictable?

---

<div class="post-metadata">

**Author:** ![Yoshiii](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/yoshiii/32/31156_2.png) [@Yoshiii](https://forum.kirupa.com/u/Yoshiii)\
**Post date:** [June 17, 2026, 7:40am UTC](https://forum.kirupa.com/t/what-s-a-clean-way-to-handle-optimistic-updates-without-mutating-my-normalized-cache/682115/2 "2026-06-17T07:40:45Z")

</div>

Mutating `state.entities[id]` is how you end up with spooky action-at-a-distance, because some component somewhere cached that object reference and now it “updated” without you meaning to.

What’s worked for me is treating optimistic updates like overlays: keep your base `entities` immutable-ish, and store optimistic edits separately as tiny patches keyed by `opId` (and usually indexed by `entityId` too). Your read path resolves “base entity + any active patches for this id”, so the only thing you copy during an optimistic rename is the one entity (or even just the patch object), and rollback is just dropping that `opId` patch. When the server confirms, you commit by writing a new `{...entity, name}` into `entities[id]` (copy-on-write for that entry) and remove the patch; retries don’t touch base state at all.

Bonus: since your `lists` are just id arrays, they stay totally stable and you avoid the “why did this list rerender” mystery.
