-1

I don't understand why below code doesn't change state. Even when 'if' statement is executed, state is the same. Why case with if doesn't change state?

class Welcome extends React.Component {
  state = {
    items: [
      {
        id: 1,
        done: false,
      },
      {
        id: 2,
        done: false,
      },
      {
        id: 3,
        done: false,
      },
    ]
  }
  
  handleDone = (index) => {
      this.setState((prevState) => {
        const copyItems = [...prevState.items];

      if (copyItems[index].done === false) {
        console.log("Done should be true");
        copyItems[index].done = true;
      } else {
        console.log("Done should be false");
        copyItems[index].done = false;
      }

      // copyItems[index].done = !copyItems[index].done - the same result

      return {
        items: [...copyItems],
      };
    });
  }

  render() {
    return (
      this.state.items.map((item, index) => {
        return (
          <div>
            <span>id: {item.id}</span>
            <span> {item.done ? "- is not done" : "- is done"} </span>
            <button 
              onClick={() => this.handleDone(index)}>
              Change to opposite
            </button>
          </div>
        )
      })
    )
  }
}


ReactDOM.render(<Welcome />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
this.setState((prevState) => {
  const copyItems = [...prevState.items];

  if (copyItems[index].done === false) {
    console.log("Done should be true");
    copyItems[index].done = true;
  } else {
    console.log("Done should be false");
    copyItems[index].done = false;
  }

  // copyItems[index].done = !copyItems[index].done - the same result

  return {
    items: [...copyItems],
  };
});

Below example works fine when there is no if statement:

this.setState((prevState) => {
  const copyItems = [...prevState.items];

  copyItems[index].done = true;
 
  return {
    items: [...copyItems],
  };
});

Below example works fine with 'if' statement in case when object is copied:

this.setState((prevState) => {
  const copyItems = JSON.parse(JSON.stringify([...prevState.items]));

  copyItems[index].done = !copyItems[index].done

  return {
    items: [...copyItems],
  };
});
CodeJoe
  • 262
  • 2
  • 10

2 Answers2

1

What's wrong for React is that even if you spread your array, the object inside it are still referenced as the object inside your state. So by doing copyItems[index].done = true; you're actually mutating the state directly (this.state[index].done should be true also). What you can do to avoid this is to use .map on your prevState so you're not updating the state directly.

const state = [{ done: true }];
const stateCopy = [...state];

const toFind = 0;

stateCopy[toFind].done = false;

console.log(stateCopy[toFind], state[toFind]); // { done: false } , { done: false } /!\ state should not be updated at this point.

// good way of doing that might be by using prevState.items.map() and do your logic inside it

const stateUpdated = state.map((el, index) => {
  if (index === toFind) {
    return { done: !el.done }
  }
  return el;
});

console.log(stateUpdated[toFind], state[toFind]) // state is not mutated, as expected
Nicolas Menettrier
  • 1,647
  • 1
  • 13
  • 28
-1

Problem was caused by create-react-app that wrap app with <React.StrictMode>.

To resolve it just remove this wrapping tag. And that's it. It's expected behaviour and it should help avoid bugs on prod. More details in React docs.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
CodeJoe
  • 262
  • 2
  • 10
  • 1
    Removing something which is supposed to help with avoiding bugs on prod does not sound like a right thing to do. – cyberskunk Jul 21 '21 at 08:50
  • @cyberskunk https://stackoverflow.com/a/65167384/7399693 – CodeJoe Jul 22 '21 at 16:23
  • what's your point? I know what `` is and that it's annoying to deal with double-invoked functions. However, it does help in avoiding side-effects. – cyberskunk Jul 23 '21 at 10:07