barurumm
barurumm

Reputation: 45

React does not trigger rerender if you assign state value to another variable and then change the state

I stumbled upon a strange behaviour due to my lack of knowledge and experience. I have a Component called inside another, looking like this:

            <CustomSelectionButtons
              values={roleTypes}
              label="Roles:"
              onSelect={setUserRoles}
              selected={roles}
            />

roles is a state variable, which is set with the onSelect method (setUserRoles). Here it is:

  const setUserRoles = (button: AccountsAndRolesUnion) => {
   const updatedRoles: AccountsAndRolesUnion[] = roles; //this is the state variable.

   /* doing something with the local **updatedRoles** array */

   setRoles(updatedRoles);
  };

If I use the code like this, the component does not rerender after setRoles hook call. However, If I assign the local variable with roles.slice(), everything works as expected, the component is rerendered. So, this works:

const setUserRoles = (button: AccountsAndRolesUnion) => {
  const updatedRoles: AccountsAndRolesUnion[] = roles.slice(); //slicing the state variable.

  /* doing something with the local **updatedRoles** array */

  setRoles(updatedRoles);
};

Can someone explain why this happens?

Upvotes: 1

Views: 2570

Answers (3)

Slice creates a shallow copy of the original array, meaning that it returns a different object than roles itself, even though its content may be the same.

When you do setRoles(roles), you are actually sending the exact same object in memory, and when React compares the props before deciding whether to re-render, it doesn't detect any difference. However, when you do setRoles(roles.slice()), you pass a different variable in memory, so React figures there is a change in props and re-renders.

Upvotes: 1

Martin
Martin

Reputation: 6146

In your first example you use the same array instance to update the state. React checks only shallow equality. It's not iterating over the items. .slice() creates a new array. Always create a new array if your state is an array.

Upvotes: 1

Nadia Chibrikova
Nadia Chibrikova

Reputation: 5036

This happens because const updatedRoles=roles creates another name for the exact same object in memory, so everything you do with updatedRoles is reflected in roles, in other words you mutate the state. roles.slice() on the other hand creates a new independent object. When you pass this new object to setState React can see the difference and trigger re-render, but when you just pass it roles by another name, React doesn't see any need to do anything because updatedRoles and roles are the same thing.

Upvotes: 1

Related Questions