React shallow copy still triggers re-render?

From what I learnt about React, you should not mutate any objects otherwise React doesn’t know to re-render, for example, the following example should not trigger re-render in the UI when button is clicked on:

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App({ input }) {
  const [items, setItems] = useState(input);

  return (
    <div>
      {items.map((item) => (
        <MyItem item={item}/>
      ))}
      <button
        onClick={() => {
          setItems((prevItems) => {
            return prevItems.map((item) => {
              if (item.id === 2) {
                item.name = Math.random();
              }
              return item;
            });
          });
        }}
      >
        Update wouldn't work due to shallow copy
      </button>
    </div>
  );
}

function MyItem ({item}) {
  const name = item.name
  return <p>{name}</p>
}

ReactDOM.render(
  <App
    input={[
      { name: "apple", id: 1 },
      { name: "banana", id: 2 }
    ]}
  />,
  document.getElementById("container")
);

You can try above code here

And the correct way to update the array of objects should be like below (other ways of deep copying would work too)

 setItems((prevItems) => {
    return prevItems.map((item) => {
               if (item.id === 2) {
                   # This way we return a deepcopy of item
                   return {...item, name: Math.random()}
               }
               return item;
            });
});

Why does the 1st version works fine and the UI is updated right away even though I’m just updating the original item object?

24 thoughts on “React shallow copy still triggers re-render?”

  1. Render happens due to .map that creates new array. If you do something like prev[1].name = "x"; return prev; in your hook, this will not perform an update. Per reactjs doc on setState with function argument:

    If your update function returns the exact same value as the current
    state, the subsequent rerender will be skipped completely.

    Reply
  2. if your setItem setting a new object, your page with a state change will re-render
    in your logic, you performed a shallow copy, which:

    • a shallow copy created a new object, and copy everything of the 1st level from old object.

    Shallow copy and deep copy also created a new object, they both trigger a re-render in React.

    The different of shallow copy and deep copy is: from 2nd level of the old object, shallow copy remains the same objects, which deep copy will create new objects in all level.

    Reply

Leave a Comment