0

I'm learning React and working on a John Conway's Game of Life app. I have a 2D array defined in the state/the constructor which creates a game board of squares. I have a function called isSquareAlive that handles whether or not a particular square in the grid is "alive" in the Game of Life sense and updates squares and sets them to being alive if the user clicks on them. I also have another function called selectBoardSize which allows the user to click a button and adjust the size of the board.

When the App mounts I generate a random board filled with some squares set to "alive" and some not. I take care of this inside componentDidMount:

  componentDidMount = () => {
    const data = this.state.board;
    // Creates random decimal number between 0 and 1
    const startingBoard = data.map(a => a.map(Math.random));
    // Rounds decimal numbers to either 0 or 1 so the grid can display whether the cell is alive or dead
    const rounded = startingBoard.map(a => a.map(Math.round));

    this.setState({
       board: rounded
    });
  }

This works fine. If the user attempts to adjust the size of the board via selectBoardSize I want to change the size of the board and then again fill it with random "alive" cells. Here is selectBoardSize:

  // Allows user to click button and change size of game board
  selectBoardSize = (width, height) => {
    this.setState({
      boardHeight: height,
      boardWidth: width,
      board: Array(this.state.boardHeight).fill(0).map(_ =>
              Array(this.state.boardWidth).fill(0))
    });
      {/*this.onChangeBoardSize(width, height);*/}
  }

When the user changes the board size I am attempting to use componentDidUpdate to grab the new board size and fill it with random "alive" cells for that board dimension, much like I do initially with componentDidMount. This is where I am having difficulty.

Here's my componentDidUpdate:

  // Attempts to fill board with random alive squares when user resizes the size of the board via onClick/selectBoardSize()
  componentDidUpdate = (prevProps, prevState) => {
    console.log('PrevState is : ' + prevProps, prevState);

    // Attempts to update board height and width and then populate board with random "alive" squares
    if(this.state.boardWidth !== prevState.boardWidth) {
      if(this.state.boardHeight !== prevState.boardHeight) {
        // Console.log runs, if statements equate to true when user resizes board
        console.log('Nested if statements in componentDidUpdate triggered');

        const boardWidth = this.state.boardWidth;
        const boardHeight = this.state.boardHeight;
        const data = this.state.board;
        // Creates random decimal number between 0 and 1
        const startingBoard = data.map(a => a.map(Math.random));
        // Rounds decimal numbers to either 0 or 1 so the grid can display whether the cell is alive or dead
        const rounded = startingBoard.map(a => a.map(Math.round));

        this.setState({
          boardWidth: boardWidth,
          boardHeight: boardHeight,
          board: rounded
        })
      }
    }
  }

The board successfully changes dimensions when the user clicks the buttons to resize it, but it does not generate random "alive" squares like it should. It just changes the height and width of the board but the board is empty.

How can I use componentDidUpdate to fill the game board with random "alive" cells when it is resized (similar to what it does when initially mounting, but sized appropriately when the board changes sizes). Is componentDidUpdate the correct approach? Is this an issue with setState being asynchronous?

Pretty sure I am overthinking this.

You can check out the code on codesandbox.

Nick Kinlen
  • 1,356
  • 4
  • 30
  • 58

1 Answers1

2

You don't need to use componentDidUpdate for that, but more on that later.

This isn't working, because you (correctly, by the way) wrapped your componentDidMount code to into two conditionals, to check if boardWidth AND boardHeight did change. Problem is, the height never changes, so it never reaches that part of the code.

Either way, if you take a closer look to your code, you are updating the state too many times without need. If you already know beforehand which size the user wants the board to be, do it all at once.

Here's what I understand you want to do every time an user changes the size:

  • Create a new board based on the desired Width and Height;
  • Fill it with random alive squares;

You only need that onChangeBoardSize function, with a minor change. Notice that you don't need to retrieve the board from state, because it'll be outdated with old width and height;

onChangeBoardSize = (width, height) => {
  // Recreate board with new width and height values
  const data = Array(height)
    .fill(0)
    .map(_ => Array(width).fill(0));
  // Creates random decimal number between 0 and 1
  const startingBoard = data.map(a => a.map(Math.random));
  // Rounds decimal numbers to either 0 or 1 so the grid can display whether the cell is alive or dead
  const rounded = startingBoard.map(a => a.map(Math.round));

  this.setState({
    boardHeight: height,
    boardWidth: width,
    board: rounded
  });
};

Codesandbox example

Tip: Watch out for too many sub-sequential state modifications. Always prefer to do it all at once than to trigger setState sequentially. Specially inside react lifecycle methods

Alexandre Wiechers Vaz
  • 1,587
  • 2
  • 18
  • 21
  • Thanks that did the trick. I was too fixated on componentDidUpdate and whether or not it was working. Thanks for the help. – Nick Kinlen Sep 25 '18 at 01:18