componentWillMount() and setting state

componentWillMount() and setting state
0

#1

I’m currently doing the Game of Life project. Here is a link to the repo if any of you need to check out the rest of the code. But let me get straight to where I’m running into an issue:

My parent component, App.js, houses my initialization and update logic of my game board. The flow starts off with building a randomized board and then setting the app state with that board. Then, right before the Culture or board component is rendered, I run my update method.

import React, { Component } from 'react';
import Culture from './Culture.js';
import './App.scss';

class App extends Component {
  constructor() {
    super()
    this.state = {
      boardState: null
    }

  }

  componentWillMount() {
    var boardState = [];
    const random = () => {
       return Math.round(Math.random() * (1 - 0 + 1) + 0)
    }
    for(var i = 0; i < 10; i++) {
        //create y axis by pushing state values to inner arrays
      boardState.push([]);
        for(var j = 0; j < 10; j++) {
            //create x axis by pushing state values to inner arrays
            boardState[i].push(random());
          }
        console.log(boardState[i]);
    }

    boardState[-1] = [];
    boardState.push([]);
    this.setState({ boardState: boardState });
    console.log(this.state.boardState);

    for(let i = 0; i < boardState[0].length; i++) {
      boardState[-1].push('NULL');
      boardState[boardState.length -1].push('NULL');
    }

  }

  processBoardState() {
    const boardState = this.state.boardState;
    var neighbors = 0;
      //check for neighbors and tally the amount
        for(let y = 0; y < boardState.length; y++) {
          for(let x = 0; x < boardState[y].length; x++) {
            console.log('['+ y + ']' + '[' + x + ']');
            try {
              switch (boardState) {
                case boardState[y-1][x-1] !== 0 && boardState[y-1][x-1] !== 'NULL':
                  console.log('['+ y - 1 + ']' + '[' + x -1 + ']');
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y][x -1] !== 0 && boardState[y][x-1] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y + 1][x - 1] !== 0 && boardState[y + 1][x - 1] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y - 1][x] !== 0 && boardState[y - 1][x] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y][x] !== 0 && boardState[y][x] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y + 1][x] !== 0 && boardState[y + 1][x] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y - 1][x + 1] !== 0 && boardState[y - 1][x + 1] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y][x + 1] !== 0 && boardState[y][x + 1] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                case boardState[y + 1][x + 1] !== 0 && boardState[y + 1][x + 1] !== "NULL":
                  neighbors++;
                  console.log(neighbors);
                  break;
                default:
                  break;
              }
            } catch(e) {
                console.log(e);

              }

        if(neighbors < 2) {
          boardState[y][x] = 0
        } else if(neighbors === 3 && boardState[y][x] === 0) {
          boardState[y][x] = 1
        } else {
          boardState[y][x] = 0
        }
        }
    }
    console.log(boardState + ' after processing of generations');
    this.setState({ boardState });
  }

  //Board component named Culture
  render() {
    return (
      <div>
        <h1>"Game of Freakin' Life"</h1>
        <Culture boardState={this.state.boardState}/>
      </div>
    )
  }
}

export default App;

The problem I am running into is that the board variable in componentWillMount() is getting updated but when I try to update the state with that variable, it doesn’t get updated. I did checkout the in-depth documentation here: https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/birth/premounting_with_componentwillmount.html and it did mention that you can set the state within componentWillMount(). Would there be a case where you can’t do it? Or is it something more deeper than what I can see?

Thanks in advance for y’all help :smiley:


#2

Haven’t tried your code, so apologies if this goes off in the wrong direction. How do you know the state didn’t update? I wouldn’t rely on a console.log immediately after the setState as proof, it might finish before setState does. If you do a console.log on the state in componentDidMount(), does it have the value you expect? Or (this is hacky), even setTimeout on the console.log statement in componentWillMount so the console.log is forced to come out a second or two behind everything else.


#3

I did try componentDidUpdate() and still ended up with getting null. The only difference was the code that created the board didn’t run. Another strange thing is if you ran the same code on Codepen, the state would actually update. I’m not sure if it is misuse of the lifecycle or it is a bug within the build environment I have it in.


#4

I’m a bit in a hurry, but treat your state as if it were immutable, search for it on Google.


#5

So you want to generate an initial state of values for your game and then update those values each tick based on the number of neighbors right?

First you generate your initial board state inside componentWillMount

componentWillMount() {
  const length = 25;
  let board = [];
  for (let i = 0; i < length; i++) {
    let row = [];
    for (let j = 0; j < length; j++) {
      row.push(Math.round(Math.random()));
    }
    board.push(row);
  }
  this.setState({ board: board });
}

Now based on the rules of the game, you need to update each cell per game “tick.” A tick is like 1 turnover of the game. A good way to do this is to create a game timer that can either start when the component is mounted or when some action occurs, like the user clicking a start button.

Javascript has a built in function called setInterval that will repeatedly call it’s argument function over an interval.

https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval

So you could use it inside of componentWillMount like this

window.setInterval(this.updateBoard, 100);

This executes the updateBoard function every 100ms. If you want to get advanced and make your game more efficient, you can write your own timer interval.

http://nokarma.org/2011/02/02/javascript-game-development-the-game-loop/

So what does the updateBoard method look like? You’re looking at the current state of the board and generating the new state of the board. Can we do that by updating each cell of the existing board? No, because each cell depends on the current state of it’s neighbors. So you make a copy of the board and update that, which is a best practice for updating state in React anyway.

updateBoard() {
  let newBoard = this.state.board;
  const length = this.state.length;
  let neighbors = null;
  let currCell = null;

  for (let i = 0; i < length; i++) {
    for (let j = 0; j < length; j++) {
      currCell = this.state.board[i][j];
      neighbors = this.checkNeighbors(i, j);

      if (currCell && neighbors < 2) {
        newBoard[i][j] = 0;
      }
      if (currCell && neighbors > 3) {
        newBoard[i][j] = 0;
      }
      if (!currCell && neighbors === 3) {
        newBoard[i][j] = 1;
      }
    }
  }
  this.setState({ board: newBoard });
}

There’s helper method checkNeighbors to handle tallying neighbors for each cell and returning the total. Because cells can exist on the edges of our board, we need to wrap to the other side of the map and also prevent checking out of bounds.

checkNeighbors(i, j) {
  let currCell = this.state.board[i][j];
  const length = this.state.length;
  let neighbors = 0;

  if (this.state.board[(i - 1 + length) % length][(j - 1 + length) % length]) {
    neighbors++;
  }
  // Code below would be for the other 7 nearby cells

  return nehbors;
}

Hopefully this helps! Let me know if you have any questions


#6

I was able to fix the state not setting. Turns out it was my misunderstanding of this.setState() and where I was testing for it.

Now, my main issue is that my board state array changes all to zeros before reaching my method that updates the board.


#7

Great news! I figured out my problem with my board going extinct on render. The original logic on tested true when one of the conditions was met. I needed to have multiple conditions to test each surrounding cell. Man do I feel so relieved :smiley:

The next obstacle I have is figuring out how to render the page by each generation. I’ll keep you guys informed.

Dan