Jawaka72
Jawaka72

Reputation: 65

Restarting setInterval after clearInterval in React

In this game I'm building, I clear the setInterval after the user loses. When they click Play Again? I want the timer to start again, but I'm having trouble making that happen while using React. I've tried several things like separating the timer into its own component, making a helper function, and using the life cycle methods, but I just can't seem to get this part. I can start and clear the setInterval just fine, but it's restarting it that's my problem here.

import React, {Component} from 'react';
// helper function to set a random action
function setRandomAction() {
    let actions = ['bop it', 'pull it', 'twist it'];
    let rando = actions[Math.floor(Math.random() * actions.length)];
    return rando;
}
class BopIt extends Component {
    constructor(props) {
        super(props);
        // set initial action in this.state so it is not empty on pageload
        this.state = {
            action: setRandomAction(),
            countdown: 3,
            userPressed: '',
            play: true
        }
        this.bind = this.keyPressed.bind(this);
        this.bind = this.keepPlaying.bind(this);
        this.bind = this.timer.bind(this);
        this.bind = this.startTimer.bind(this);
        this.bind = this.endGame.bind(this);
        this.quitGame = this.quitGame.bind(this);
        this.playAgain = this.playAgain.bind(this);
    }
    componentDidMount() {
        this.keyPressed();
        this.startTimer();
    }
    startTimer() {
        let setTimerTime = parseInt(`${this.state.countdown - 2}000`);
        this.stopIntervalId = setInterval(() => this.timer(), setTimerTime);
    }
    componentWillUnmount() {
        this.startTimer();
        this.keyPressed();
        this.keepPlaying();
        this.endGame();
    }

    timer() {
        var count = this.state.countdown;
        if (count === 0) {
            count = 4
        }
        this.setState({countdown: count - 1});
    }
    keyPressed() {
        document.addEventListener('keyup', (e) => {
            if (e.key === 'ArrowLeft') {
                this.setState({
                    userPressed: 'pull it'
                });
            } else if (e.key === 'ArrowDown') {
                this.setState({
                    userPressed: 'bop it'
                });
            } else if (e.key === 'ArrowRight') {
                this.setState({
                    userPressed: 'twist it'
                });
            } else {
                // this.endGame();
                this.setState({
                    userPressed: 'wrong'
                });
            }
            if (this.state.userPressed !== this.state.action) {
                this.endGame();
            } else {
                this.keepPlaying();
            }
        });
    }
    keepPlaying() {
        let actions = ['bop it', 'pull it', 'twist it'];
        let rando = actions[Math.floor(Math.random() * actions.length)];
        this.setState({
            action: rando,
            userPressed: ''
        });
    }
    endGame() {
        console.log('You Lost!!!');
        this.setState({
            play: false
        });
    }
    quitGame() {
        clearInterval(this.stopIntervalId);
    }
    playAgain() {
        this.setState({
            play: true,
            action: setRandomAction(),
            countdown: 3
        });
    }
    render() {
        // if (this.state.countdown <= 0) {
        //   this.endGame();
        // }
        console.log(this.state)
        let gameAction = `${this.state.action} ${this.state.countdown}`;
        return (
          <div className="bop-it">
            <div className="show-action">
            {this.state.play ?  gameAction : <ResetGame playAgain={this.playAgain} quitGame={this.quitGame}/> }
            </div>
            <span>Pull It</span>
              <br/>
            <span>Bop It</span>
              <br/>
            <span>Twist It</span>
          </div>
        );
    }
}
class ResetGame extends Component {
    render() {
        return (
          <div>
            <input type="button" value="Play Again?" onClick={this.props.playAgain}/>
            <input type="button" value="Quit Game?" onClick={this.props.quitGame}/>
          </div>
        );
    }
}
export default BopIt

EDIT:

I ended up simply calling this.startTimer() at the end of the playAgain() method. I could have sworn I did that previously, but apparently not. I'm also making sure to only call clearInterval in one place so there are no conflicts in other parts of the app. Another issue I was having was that whenever I got setInterval to restart, the timer was counting down at a faster rate. That was because of this: let setTimerTime = parseInt(`${this.state.countdown - 2}000`); this.stopIntervalId = setInterval(() => this.timer(), setTimerTime); I put that line of code there because I eventually want the user to choose the game speed, but this was totally messing up the countdown property in my app's state object. Removing this line for now has cleared up some confusion too.

import React, {Component} from 'react';
// helper function to set a random action
function setRandomAction() {
  let actions = ['bop it', 'pull it', 'twist it'];
  let rando = actions[Math.floor(Math.random() * actions.length)];

  return rando;
}

class BopIt extends Component {
  constructor(props) {
    super(props);
    // set initial action in this.state so it is not empty on pageload
    this.state = {
      action: setRandomAction(),
      countdown: 3,
      userPressed: '',
      play: true
    }
    this.bind = this.keyPressed.bind(this);
    this.bind = this.keepPlaying.bind(this);
    this.bind = this.endGame.bind(this);
    this.bind = this.timer.bind(this);
    this.bind = this.startTimer.bind(this);
    this.quitGame = this.quitGame.bind(this);
    this.playAgain = this.playAgain.bind(this);
  }
  componentDidMount() {
    this.keyPressed();
    this.startTimer();
  }
  startTimer() {
    // let setTimerTime = parseInt(`${this.state.countdown - 2}000`);
    this.stopIntervalId = setInterval(() => this.timer(), 1000);
  }
  componentWillUnmount() {
    this.keyPressed();
    this.keepPlaying();
    this.endGame();
  }
  timer() {
    let count = this.state.countdown;
    if (count === 0) {
      count = 4
      // end the game if the timer hits 0
      this.endGame();
    }
    this.setState({countdown: count - 1});
  }
  keyPressed() {
    document.addEventListener('keyup', (e) => {
      if (e.key === 'ArrowLeft') {
        this.setState({
          userPressed: 'pull it'
        });
      } else if (e.key === 'ArrowDown') {
        this.setState({
          userPressed: 'bop it'
        });
      } else if (e.key === 'ArrowRight') {
        this.setState({
          userPressed: 'twist it'
        });
      } else {
        this.setState({
          userPressed: 'wrong'
        });
      }
      // if user presses wrong key, then the game is over
      if (this.state.userPressed !== this.state.action) {
        this.endGame();
      } else {
        // otherwise, reset the time and chooose a random action
        this.keepPlaying();
      }
    });
  }

  keepPlaying() {
    this.setState({
      action: setRandomAction(),
      countdown: 3,
      userPressed: ''
    });
  }
  endGame() {
    console.log('You Lost!!!');
    this.setState({
      play: false
    });
    clearInterval(this.stopIntervalId);
  }
  quitGame() {
    // clearInterval(this.stopIntervalId);
    console.log('you have left the game')
  }
  playAgain() {
    this.setState({
      play: true,
      action: setRandomAction(),
      countdown: 3
    });
    this.startTimer();
  }
  render() {
    let gameAction = `${this.state.action} ${this.state.countdown}`;
    return (
      <div className="bop-it">
        <div className="show-action">
        {this.state.play ?  gameAction :
          <ResetGame
            playAgain={this.playAgain}
            quitGame={this.quitGame}
          />
        }
        </div>
        <span>Pull It</span>
          <br/>
        <span>Bop It</span>
          <br/>
        <span>Twist It</span>
      </div>
    );
  }
}


class ResetGame extends Component {
  render() {
    return (
      <div>
        <input type="button" value="Play Again?" onClick={this.props.playAgain}/>
        <input type="button" value="Quit Game?" onClick={this.props.quitGame}/>
      </div>
    );
  }
}

export default BopIt

Upvotes: 2

Views: 1876

Answers (1)

Alejandro Vales
Alejandro Vales

Reputation: 2937

I think the problem might be that you are setting 2 timers, at the same time, one in componentWillUnmount and another one in componentWillMount and when you delete them you only delete one of them because the other gets lost by variable overwritting

I would change the following methods on your code, so that it's a little more difficult to create duplicate timers and so that it also triggers the timer again after playing again

startTimer() {
    let setTimerTime = parseInt(`${this.state.countdown - 2}000`);
    if (!this.stopIntervalId) {
        this.stopIntervalId = setInterval(() => this.timer(), setTimerTime);
    }
}
quitGame() {
    clearInterval(this.stopIntervalId);
    this.stopIntervalId = undefined;
}
playAgain() {
    this.setState({
        play: true,
        action: setRandomAction(),
        countdown: 3
    });
    this.startTimer()
}

Upvotes: 1

Related Questions