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?

2 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