Immutable State Updates

Immutable state updates mean never changing a state value in place — instead you create a new array or object with the change applied. React compares state by reference , so a brand-new value is how it detects a change and triggers a re-render; mutating the existing value silently skips it.

Learn Immutable State Updates in our free React course — an interactive lesson with runnable examples, a practice exercise and a quick recall.

Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1️⃣ Why Mutation Skips the Re-render

React's state setter checks whether the new value is the same reference as the old one (an Object.is comparison). If you push into the existing array or assign to obj.x , the reference is unchanged, so React concludes nothing happened and does not re-render. Creating a new value flips that comparison to "changed."

2️⃣ Immutable Array Updates

Three moves cover almost everything, and each returns a new array: add with spread [...items, x] or concat ; remove with filter ; update one item with map , replacing only the matching element. Avoid push , splice , sort , and reverse on state — they mutate in place.

3️⃣ Nested Objects & Shallow Copies

Spread makes a shallow copy: the top level is new, but nested objects are still shared. So to change something deep, you must spread every level along the path you are editing, leaving untouched branches shared (which is fine and efficient). Skip a level and you will accidentally mutate the original's nested object.

These lines immutably toggle one todo's done flag and save it. Put them in the right order:

📋 Quick Reference

1. Why does push + setState often do nothing?

It mutates the same array, so the reference is unchanged and React bails out of the re-render.

With filter , which returns a new array excluding the item.

No, it is shallow. Spread every nested level you intend to change.

Build a reducer that adds, removes, and updates cart items without ever mutating the previous state. Run it and check your output.

Practice quiz

What does it mean to update React state immutably?

  • Change the existing value in place
  • Freeze the state so it can never change
  • Create a new array or object with the change applied, instead of mutating the old one
  • Use a global variable instead of state

Answer: Create a new array or object with the change applied, instead of mutating the old one. Immutable updates produce a brand-new value rather than modifying the existing one in place.

How does React decide whether state changed?

  • A reference comparison (Object.is) of the new value vs the old
  • A deep equality check of every field
  • By hashing the value
  • By comparing the rendered HTML

Answer: A reference comparison (Object.is) of the new value vs the old. React compares by reference with Object.is; a same-reference value looks unchanged and skips the re-render.

Why does calling array.push(x) then setItems(array) often render nothing?

  • push is too slow
  • setItems ignores arrays
  • push throws an error in React
  • push mutates the same array, so the reference is unchanged and React bails out

Answer: push mutates the same array, so the reference is unchanged and React bails out. push mutates in place, leaving the same reference, so React sees no change and skips the update.

Which is the immutable way to ADD an item to a state array?

  • items.push(x)

Spreading into a new array, [...items, x], adds an item without mutating the original.

Which method immutably REMOVES an item from an array?

  • filter
  • splice
  • pop
  • shift

Answer: filter. filter returns a new array excluding the unwanted item; splice/pop/shift all mutate in place.

Which method immutably UPDATES one item in an array?

  • forEach
  • sort
  • map
  • reverse

Answer: map. map returns a new array, replacing only the matching element and copying it with the spread.

Is the spread operator a deep copy?

  • Yes, it copies all nested levels
  • No, it is a shallow copy — nested objects are still shared references
  • Only for arrays
  • Only for objects

Answer: No, it is a shallow copy — nested objects are still shared references. Spread copies only the top level; nested objects remain shared, so you must spread each level you change.

To change state.user.prefs.theme immutably, what must you do?

  • Just set state.user.prefs.theme = 'dark'
  • Replace the entire state with a primitive
  • Call setState twice
  • Spread each level along the path: { ...state, user: { ...state.user, prefs: { ...state.user.prefs, theme } } }

Answer: Spread each level along the path: { ...state, user: { ...state.user, prefs: { ...state.user.prefs, theme } } }. Because spread is shallow, you copy every level along the path you edit; untouched branches can stay shared.

Which array methods should you avoid calling directly on state because they mutate?

  • map and filter
  • sort, reverse, push, and splice
  • concat and slice
  • join and indexOf

Answer: sort, reverse, push, and splice. sort, reverse, push, and splice mutate the array in place; copy first (e.g. [...items].sort()).

When you spread an unchanged nested branch's reference into new state, that is...

  • A bug that causes stale data
  • Only allowed in React 19
  • Fine and efficient — sharing untouched branches is expected
  • Going to cause an infinite loop

Answer: Fine and efficient — sharing untouched branches is expected. Sharing references for branches you did not change is correct and efficient; only copy along the edited path.