coderboy_4200
coderboy_4200

Reputation: 53

React setState doesn't work when mutating the state directly why not

Code, i am trying to copy the list as another list and modifying the name property, I was wondering why would this not work, i know that shallow copying this works/ also when i use a fn updater it works , thanks


    import { useState } from "react";
    
    const initList = { name: "abhi" };
    
    export default function List() {
      const [list, setList] = useState(initList);
    
      function handleClick() {
        const nextList = list;
        nextList.name = "anand";
        // setList((list) => ({ ...list }));
        setList(nextList);
      }
    
      return (
        <>
          <button onClick={handleClick}>change name</button>
          <ul>{list.name}</ul>
        </>
      );
    } 

Edit: I understand the what and how, wanted to know why this happens in Reactjs. I have got my answer, thank you so much guys for helping me out

Upvotes: 1

Views: 102

Answers (3)

apieceofbart
apieceofbart

Reputation: 2197

You've got answers already but to be precise here's the piece of source code from react repo that I believe is responsible for what you're seeing:

// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
  markWorkInProgressReceivedUpdate();
}

You can find the definition of is function here. The important bit is that it compares two passed objects (old state and a new state) by reference. Since you did not make a shallow copy of the new state these are in fact the same and no update is made.

Perhaps an interesting thing is that using functional version of setState does not automatically save you from the same mistake. It also has to return a different reference from the old state. In other words, this also won't update state:

const nextList = list;
nextList.name = "anand";
setList((list) => nextList);

Upvotes: 3

Lucas Nery
Lucas Nery

Reputation: 1

I didn't quite understand your question, but you would like to keep the object's initial value { name: "abhi" } and on const nextList change name to "anand", right? getting like this: initList = { name: "abhi" } and list = { name: "anand" }? If so, just do this:

import { useState } from "react";

const initList = { name: "Test 1" };

export default function List() {
  const [list, setList] = useState({ ...initList });

  function handleClick() {
    const nextList = list;
    nextList.name = "Test 2";
    // setList((list) => ({ ...list }));
    setList(nextList);
  }

  return (
    <>
      <button onClick={handleClick}>change name</button>
      <ul>{list.name}</ul>
      <ul>{initList.name}</ul>
    </>
  );
} 

This happens because you are inserting the object and its pointer to the state of the list, in this case what you need is just the initial values ​​of the object and not the complete one.

Upvotes: 0

e-motta
e-motta

Reputation: 7520

const nextList = list will not create a new object, it will create a variable nextList that points to the same object as list.

Consequently, when you mutate the object referenced by nextList, you're mutating the same object referenced by list.

Read more here.

And, as you stated, you already know state in react shouldn't be mutated.

Upvotes: 1

Related Questions