0

I am trying to make a simple game like purple pairs in Purple palace game using React. I have a method named clickBtn, it is supposed to increment the count on click and decrement on second click but I don't know why the handleClick method is changing chosen and clicked property of state even if a copy of the new state is made without using setState method. Can you help me fix this?

class GameBoard extends React.Component {
  constructor() {
    super();
    this.state = {
      score: 0,
      time: 0,
      list: [...generateObList(), ...generateObList()],
      count: 0
    };
  }

  handleClick = (id) => {
    this.clickBtn(id);
    const list = this.state.list.slice();
    const current = list.find((a) => a.id === id);
    for (let x of list) {
      if (x.clicked && x.value == list.find((a) => a.id == id).value) {
        x.chosen = true;
        x.clicked = false;
        current.chosen = true;
        current.clicked = false;
        // this.setState((prev) => ({
        //   list: prev.list.map((el) =>
        //     el.id === id ? current : el.value === current.value ? x : el
        //   ),
        //   score: prev.score + 1
        // }));
      }
    }
  };

  clickBtn = (id) => {
    const current = this.state.list.slice().find((e) => e.id === id);
    let deClick = current.clicked;
    current.clicked = !current.clicked;
    console.log(current);
    this.setState(
      (prev) => ({
        list: prev.list.map((el) => (el.id === id ? current : el)),
        count: prev.count + (deClick ? -1 : 1)
      }),
      () => {
        console.log(this.state.count, deClick, current);
      }
    );
  };

  render() {
    const boardStyle = {
      gridTemplateColumns: `repeat(5, 1fr)`,
      gridTemplateRows: `repeat(5,1r)`
    };
    let list = this.state.list.map((n) => (
      <Card
        value={n.value}
        onClick={(e) => {
          this.handleClick(n.id);
        }}
        show={!n.chosen}
      />
    ));
    return (
      <div class="gameBoard" style={boardStyle}>
        {list}
      </div>
    );
  }
}
Saurav Kumar
  • 125
  • 1
  • 2
  • 10

1 Answers1

1

slice() just return a shallow copy of the array so both original and copy refer to the same array items if the items are reference type ( objects ).

For object slice copies object references into the new array. Both the original and new array refer to the same object. If a object changes, the changes are visible to both the new and original arrays.

Try to deep copy the objects. You can easily do deep copy by using, JSON.parse(JSON.stringify(arr))

Also you can try lodash's cloneDeep() method.

For more details about deep cloning - https://stackoverflow.com/a/122704/11306028

Dilshan
  • 2,797
  • 1
  • 8
  • 26
  • "Easily do deep copy" comes with a lot of caveats. That approach will not properly handle nested references to objects, or instantiated classes like `Date`, to name a few. It may work if you have very simple data constructs with literal string and number values, but be careful. Also, you ought to mention that updating React state directly is a terrible, terrible idea and an anti-pattern. `setState()` should ALWAYS be used to update state. – jered Nov 11 '20 at 03:27
  • Yes. That's why I also mention about `cloneDeep` & added reference to a detailed answer about deep copying. And also, because he do the shallow copy, he update the state directly. If he did the deep copy the variable, `current` then it should work as he use `setState` to update it. – Dilshan Nov 11 '20 at 03:34
  • I wanted to mention that I tried using `rest` operator because I wanted to use a copy of the state in another component. It didn't work but when I used the `JSON parse` mentioned in the answer it worked. The object had nested objects but not more than 2 levels. I was shocked that the rest didn't work. JSON will not make good copies with null, undefined and function / property values – jack blank Jun 06 '21 at 00:32