1

I'm attempting to build a small react app to model Conway's Game of Life. I've set up a two-dimensional array to track the state of each cell in a 10-by-10 grid.

I'm attempting to store this array in the State. At each "tick" of the game, I want to make a copy of the array, evaluate each cell, potentially giving it a new value, and then assign the copy back to state. I'm basing this off of the official React Tutorial where they use this exact approach:

handleClick(i) {
    //Make a copy from state
    const squares = this.state.squares.slice();
    //Make some changes to it
    squares[i] = 'X';
    //Set state to the new value
    this.setState({squares: squares});
}

My initial approach was to use slice() as in the example above. Through debugging, I discovered that this didn't work; somehow state is being changed even though I have used various methods to copy it that shouldn't make changes to it. (I understand that if I say var x = this.state.blah and x = 5 that I have changed state because blah is a reference to it)

Here is my code:

doTick = () => {
    console.log("doin a tick");
    console.log(this.state.squares);
    //None of these approaches works
    //Three different copy strategies all fail
    //const newSquares = Object.assign({}, this.state.squares);
    //const newSquares = [...this.state.squares];
    //const newSquares = this.state.squares.slice();
    const newSquares = this.state.squares.slice();
    const origSquares = [...this.state.squares];
    //Iterating over the array
    for (var i = 0; i < 10; i++) {
      for (var j = 0; j < 10; j++) {
        newSquares[i][j] = evaluateCell(origSquares[i][j], this.countLiveNeighbors(i, j, origSquares));
        //evaluateCell(origSquares[i][j], this.countLiveNeighborsAndLog(i, j, origSquares));
      }
    }

    //this.setState({
      //squares: newSquares
    //});
  }

Even though the setState() call is commented out, just having the assignment of newSquares[i][j] = //... is enough to somehow modify state.

Here's the code where I set up the initial array in the constructor for the Board component:

  constructor(props) {
    super(props);
    var array = new Array(10);
    for (var i = 0; i < 10; i++) {
      array[i] = new Array(10).fill(false);
    }
    this.state = {
      squares: array
    };
    console.log(this.state.squares);
  }

I took a look here but I'm not having any trouble updating the squares based on clicks (that part of my code works fine). Various SO posts and in-person troubleshooters suggested the three different copy strategies that all produce the same problem. I also took a look here.

I'm very new to React and not very skilled in JS generally, and obviously I don't have a good handle on State. Here are my questions:

  1. How can I make a copy of state/part of state/data in state in such a way that the copy is not a reference to state? I want to be able to change this new data without changing state (until I am ready).
  2. Why are the methods used above NOT working properly? The documentation for slice() assures me that I'm getting a copy and not a reference.

Thanks in advance! I'm very confused.

erronius
  • 49
  • 2
  • 7

1 Answers1

0

The spread operator only does a shallow copy of the values. This means if you have any nested values in them, they will be referenced rather than copied. For instance:

const a = { field: { innerField: 'test' } };
const b = { ...a } // b ===  { field: { innerField: 'test' } } SAME field as a

To copy a nested array you should use deep copy methods, such as Lodash's cloneDeep or Ramda's clone

for example, with Lodash's cloneDeep:

const newSquares = _.cloneDeep(this.state.squares);
Dor Shinar
  • 1,474
  • 2
  • 11
  • 17
  • Thanks for the response. I did encounter Lodash's cloneDeep in my research as well. Two questions: 1. Why does `slice()` not work? The documentation says that it returns a copy of the values, not a reference. 2. Is there really no simple/easy way in regular JS to create a deep copy? It really boggles the mind that you would need to import some external library to perform what seems like a basic task. – erronius Feb 18 '19 at 20:05
  • `slice()` does the same as `...` - shallow copy. Unfortunately I don't know any built-in way of doing so. – Dor Shinar Feb 18 '19 at 20:07