EasyState & Immutability🐛 Debug Challenge

Direct state mutation — React doesn't detect change

Buggy Code — Can you spot the issue?

// 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
});

Fixed Code

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 Explained

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.

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab