Skip to content
wannacode

Updating objects and arrays

In short: An object or array in React state is updated with a new copy (spread, map, filter), because React compares references and does not see a change to the old one.

Problem

"Remove" does not remove

The cart has three lines, each with a "Remove" button. Press "Remove" on the bread.

React

The console says lines: 2. The screen shows all three lines and the old total. The data changed, the screen does not follow it, and there is no error.

remove takes the line out with splice and calls setCart(cart). The new value was passed. Why does React not draw?

The idea

How the library sees a change

With a number it is clear: 1 and 2 are different. An array and an object, though, are references: the variable points to an object in memory. There are two questions. Does the variable point to another object? And did the contents of the same object change? Each library asks in its own way, and here the difference shows in the code.

React asks only the first question. On setCart(next) it compares the old and the new state with Object.is. For an array that means "is it the same object", not "does it have the same contents".

splice changes the contents of the same array. setCart(cart) passes the same reference, Object.is says "the same", and React skips the render. That is why in React the array and the objects in state are not changed in place. You make a new array, and React sees a new reference. This is called an immutable update.

See it

See: the same array or a new one

const [cart, setCart] = useState([bread]);
function addMilk() {
cart.push(milk);
setCart(cart);
}
function addCheese() {
setCart([...cart, cheese]);
}
Step by step, in words
  1. cart points to an array with one line. React keeps this reference as the component’s state, and the screen shows the bread.
  2. cart.push(milk) adds a line to the same array. The contents changed, but it is the same object.
  3. setCart(cart) passes the same reference. React compares the old and the new one with Object.is: the same object.
  4. The same means “nothing new”: React skips the render. The milk is in the array, but not on the screen.
  5. [...cart, cheese] makes a new array: a copy of the lines plus the cheese.
  6. setCart with the new array: Object.is says “different”, and React draws again.
  7. Now the screen shows three lines. The milk too, which until now was hidden in the old array.
  8. The rule in React: do not change the state’s array, give it a new one.
The milk is in the array since the push, but it appears on the screen only with the cheese. Code with a mutation does not simply fail. It hides a change that comes out later, on a completely different action.

The idea

A new array, a new object

The cart has three actions: add a line, change the quantity of one line, remove a line.

// add: a new array with the old lines and the new one
setCart([...cart, { ...product, qty: 1 }]);
// change one line: a new array, the changed line is a new object
setCart(cart.map((line) => (line.id === id ? { ...line, qty: line.qty + 1 } : line)));
// remove: a new array without the line
setCart(cart.filter((line) => line.id !== id));
  • [...cart, x] is a new array: the old elements and x at the end.
  • { ...line, qty: 5 } is a new object: the fields of line, with qty changed.
  • map and filter always return a new array. That is why they are the main tools here.

Try it

Fix "Remove"

This is the cart from the start. Fix remove so that the line disappears and the total is computed again. The check removes the bread, then the cheese.

React

Try it

Find the bug

"Remove" does not remove. Which line is to blame?

Pick the line with the bug.

The idea

One line of the array

The customer presses "+" on the bread. The qty of one object has to change, and the object is in an array.

cart.find(...).qty++ changes the object in place: it is the same object React keeps as the old state. The array is the same, so React does not draw. The number does not vanish: it reaches the screen on the next render for another reason, and the quantity "jumps".

Be careful with copies too. [...cart] is a new array, but the lines in it are the same objects. This is a shallow copy: only the first level is copied. copy[0].qty++ changes the line in the old state too. The changed line has to be a new object: { ...line, qty: line.qty + 1 }. The lines that do not change stay the same objects, and that is fine.

More precisely, if you are curious

Object.is. Like ===, with two differences: Object.is(NaN, NaN) is true, and Object.is(0, -0) is false. For arrays and objects both compare references.

Nested objects. Every level on the path to the change is new: { ...order, address: { ...order.address, city: 'Varna' } }. The levels to the side stay the same.

structuredClone(cart) makes a deep copy: the array and every object in it. It works, but it copies everything on every change, and it cannot copy functions. For one line map and ... are cheaper.

Immer. The Immer library lets you write as if mutating (draft[0].qty++), and it makes the new objects for you. It is not in the sandbox.

It works in Vue too. The new array is correct in Vue as well: cart.value = cart.value.filter(...). The other way round does not hold: the mutation, normal in Vue, is not seen in React.

Try it

"+" and "−"

Write more and less. "+" raises the quantity of the line by 1, and "−" lowers it by 1. When the quantity is 1, "−" removes the line. The total follows the quantity.

React

Exercise

Catalogue and cart

The catalogue has four products with an "Add" button (the name for the screen reader: "Add White bread"). Write add and remove. The first click on "Add" makes a line with quantity 1, and the next ones raise the quantity of the same line instead of making a new one. "Remove" removes the line. When the cart is empty, it says "The cart is empty".

React

Test

Check yourself

1. cart.push(line); setCart(cart); Why does the screen not change?
2. Which one raises the quantity of one line correctly?
3. const next = [...cart]; next[0].qty++; setCart(next); What is wrong?
4. How do you remove a line without changing the array in state?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·