Search code examples
javascriptreactjsecmascript-6setintervalmount

React.js - How to stop setInterval and resume it after click


I am developing Game of Life using React.js. Everything works so far, but I would like to add extra feature which would allow to stop the game (cycle) and resume it if needed. And I don't know how can I do it. So inside the main class I have a method / function that starts the game and which is triggered after clicking a button defined in render():

  constructor(props) {
    super(props);
    this.state = {cellStates: Array(TOTAL_FIELDS).fill(0), cellID: Array(TOTAL_FIELDS), gameRunning: false, counter: 0}; //set and fill initial states

    this.changeState = this.changeState.bind(this);

    this.resetGame = this.resetGame.bind(this);
    this.startGame = this.startGame.bind(this);
    this.stopGame = this.stopGame.bind(this);
    this.showGameStatus = this.showGameStatus.bind(this);

    for (let o = 1; o <= TOTAL_FIELDS; o++)
    {
      this.state.cellID[o-1] = o;
    }
  }

startGame() {
    function checkZeros(arr) { //check if board is blank
      return arr === 0;
    }

    if (!this.state.cellStates.every(checkZeros)) {
      alert ('Game starts!');
      let cycle = this.state.counter; //define number of life cycle

      this.setState(() => {
        return {
          gameRunning: true //game is on
        };
      });

      $('#reset-button').attr("disabled", "disabled");
      $('#start-button').attr("disabled", "disabled");
      $('#stop-button').removeAttr("disabled"); //handle buttons


      let goGame = setInterval(() => {
        const newStates = [];

        for (let x = 0; x < TOTAL_FIELDS; x++) {
          if (this.state.cellStates[x] === 0) {  //cell is dead
            if (this.calculateCellsAround(x) === 3)
            {
              newStates.push(1);
            }
            else {
              newStates.push(this.state.cellStates[x]);
            }
          }
          else {  //cell is alive
            if (this.calculateCellsAround(x) === 3 || this.calculateCellsAround(x) === 2)
            {
              newStates.push(this.state.cellStates[x]);
            }
            else {
              newStates.push(0);
            }
          }
        }
        this.setState(() => {
            return {cellStates: newStates, counter: cycle++};
        });
      }, 50);
    }
    else {
      alert ("Please fill at least one cell to launch a game.")
    }
  }

  render() {
    return (
      <div style={{marginTop:0+"px"}} key="wrap-it-all">
        <Header />
        <Grid>
          <Row>
            <Col md={12}>
              <div className="board-outline">
                {this.createMap(140, TOTAL_FIELDS)}
              </div>
            </Col>
          </Row>
          <Row>
            <Col md={12}>
              <button id="start-button" onClick={() => this.startGame()}>Start</button>
              <button id="stop-button" onClick={() => this.stopGame()}>Stop</button>
              <button id="reset-button" onClick={() => this.resetGame()}>Reset</button>
              <p style={{color:`#fff`}}>Lifecycle: <span className="lifecycle">{this.state.counter}</span>. Game is <span className="gamerun">{this.showGameStatus()}</span>.</p>
              <Alert className="disclaimer" bsStyle="warning">
                <strong>Instruction:</strong> Select the initial state of cells and press the [Start] button.
              </Alert>
            </Col>
          </Row>
        </Grid>
      </div>
    );
  }

I assume it all has something to do with mount/unmount built in functions but I have no idea how to use them in order to make it all working.


Solution

  • You need to cancel the interval using clearInterval(handle) on stop. If you would like to resume just use setInterval(callback, tick) again.

    In addition, just change your anonymous function (aka setInterval(() => {...}) to

    function onTimerTick() {
       //copy everything marked as "..." here
    }
    

    and use setInterval(onTimerTick, 50).