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?

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

Leave a Comment