// Simulates React state update detection via Object.is
let state = { todos: [{ id: 1, text: 'Buy milk', done: false }] };
function setStateBuggy(updater) {
const newState = updater(state);
if (Object.is(state, newState)) {
console.log('no update detected'); // bailed out!
} else {
state = newState;
console.log('updated: todo done=' + state.todos[0].done);
}
}
// Bug: mutating state directly and returning the same reference
setStateBuggy(prev => {
prev.todos[0].done = true; // mutation!
return prev; // same reference returned
});let state = { todos: [{ id: 1, text: 'Buy milk', done: false }] };
function setStateFixed(updater) {
const newState = updater(state);
if (Object.is(state, newState)) {
console.log('no update detected');
} else {
state = newState;
console.log('updated: todo done=' + state.todos[0].done);
}
}
// Fix: create new references all the way down
setStateFixed(prev => ({
...prev,
todos: prev.todos.map(todo =>
todo.id === 1 ? { ...todo, done: true } : todo
)
}));Bug: prev.todos[0].done = true mutates the original state object. Returning prev means Object.is(state, newState) is true — React sees no change and bails out of re-render.
Explanation: Spreading at every level creates a new root object. Object.is(state, newState) is now false — React detects the change and re-renders.
Key Insight: Never mutate React state. Always return new objects/arrays. React uses reference equality to detect changes — mutated state appears unchanged.