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. Any help is appreciated.
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.endGame.bind(this);
this.quitGame = this.quitGame.bind(this);
this.playAgain = this.playAgain.bind(this);
}
componentDidMount() {
let setTimerTime = parseInt(`${this.state.countdown - 2}000`);
this.stopIntervalId = setInterval(() => this.timer(), setTimerTime);
this.keyPressed();
}
componentWillUnmount() {
this.keyPressed();
this.keepPlaying();
this.endGame();
}
timer() {
let 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.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
});
clearInterval(this.stopIntervalId);
}
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
These four lines are binding their respective methods to the BopIt component. This is one way to bind methods to a component in React.
I got it to work now, here’s my updated code:
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() {
this.stopIntervalId = setInterval(() => this.timer(), 1000);
}
componentWillUnmount() {
this.keyPressed();
this.keepPlaying();
this.endGame();
}
timer() {
let 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.setState({
userPressed: 'wrong'
});
}
if (this.state.userPressed !== this.state.action) {
this.endGame();
} else {
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() {
// 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
What i’ve done differently is add a startTimer
method to the BopIt component. I then call this method in componentDidMount() so it is fired the first time the game is run, then I call it again in the playAgain() method. I thought I did this exact same thing a while ago when I was trying to figure this out, but I must have done something wrong then. Anyways, now startTimer() controls whether or not setInterval should be run.
Hey @jawaka72,
I disagree with your assessment about the 4 lines @camperextraordinaire is pointing out. Glad to hear your setInterval is working.
Hi @Ethanefung
Would it be more accurate to say that those 4 lines represent how to attach eventHandlers to a component in React? The whole purpose of this personal project is for me to learn React. So, if you have any explanation of what @camperextraordinaire was pointing out, by all means share. I’m here to learn!
And thank you for your candidness.
I think this is not so much a React issue as it is a Javascript issue.
Try running the following code, and solidifying an explanation. Then refer back to the four lines pointed out by @camperextraordinaire.
const obj = {}
obj.reference = 'hi';
console.log(obj.reference);
obj.reference = 'hello';
console.log(obj.reference);
compared to the four lines
this.bind = this.keyPressed.bind(this);
this.bind = this.keepPlaying.bind(this);
this.bind = this.timer.bind(this);
this.bind = this.endGame.bind(this);
1 Like
So, the only method that is actually binding is the last one because I’m just overwriting the previous methods by applying this.bind = this.whatever.bind(this);
?
I just ran this in my code
console.log(this)
this.bind = this.keyPressed.bind(this);
console.log(this)
this.bind = this.keepPlaying.bind(this);
console.log(this)
this.bind = this.endGame.bind(this);
console.log(this)
this.bind = this.timer.bind(this);
console.log(this)
this.bind = this.startTimer.bind(this);
and the name of the bind method in all of the console.logs is bound startTimer
. Only the final method is attached to the React component.
I thought that this was how a component’s methods were built in React.
1 Like
I just commented them out and the app works fine. I thought that this was how methods were created in React, but that is not the case.
Yes, I definitely see what you mean now. I’m now looking more closely at what I’m logging from before and I can clearly see
this.playAgain = this.playAgain.bind(this);
are attached as their own methods to the component object. Now, I’m starting to wonder if I even need all of these methods to be bound like this in the first place. I’ll have to dig through the React documentation to be sure.