Why this matters

The bug. The spread on line 5 returns a *new* outer object, which looks correct. But state.items.push(todo) already mutated the inner array — same reference as the caller's previous state. Redux DevTools shows the change but the React reconciler diff sees items === items and skips updates for that subtree. Worse: time-travel debugging is now lying, because old snapshots share the mutated array.

The fix. Build a new array with spread or concat. Immer (or Redux Toolkit, which uses Immer) lets you write the push syntax and produces an immutable result under the hood — but only if you're inside its produce/createSlice boundary.

Heuristic. In any function that takes state and returns state, treat the input as Readonly.

Review heuristic

Library functions that take an object and mutate it should be loud about it (named sortInPlace, not sort). Sites that pass a config object through a chain of middleware should not allow any link in the chain to mutate it.