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
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()
}
Related
I am new to react and decided to practice by implementing a simple stop watch using both class and functional components.
I successfully implemented the stop watch using a class component. Below is the code:
Class Component
class Stopwatch extends Component {
state = {
status: false,
ms: 0,
seconds: 0,
minutes: 0,
};
stopms;
stopSeconds;
stopMinutes;
handleClick = () => {
this.changeStatus();
if (this.state.status) {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
} else {
this.stopms = setInterval(this.changeMs, 1);
this.stopSeconds = setInterval(this.changeSeconds, 1000);
this.stopMinutes = setInterval(this.changeMinutes, 60000);
}
};
changeStatus = () => {
return this.setState((state) => {
return { status: !state.status };
});
};
changeMs = () => {
return this.setState((state) => {
if (state.ms === 99) {
return { ms: 0 };
} else {
return { ms: state.ms + 1 };
}
});
};
changeSeconds = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { seconds: 0 };
} else {
return { seconds: state.seconds + 1 };
}
});
};
changeMinutes = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { minutes: 0 };
} else {
return { minutes: state.minutes + 1 };
}
});
};
handleReset = () => {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
this.setState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
componentWillUnmount() {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
}
render() {
return (
<div>
<h1>
{this.state.minutes} : {this.state.seconds} .{" "}
<span>{this.state.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={this.handleClick}>
{this.state.status === false ? "Start" : "Pause"}
</button>
<button className="btn btn-lg btn-dark" onClick={this.handleReset}>
Reset
</button>
</div>
);
}
}
export default Stopwatch;
Now I'm trying to implement the same code above but using a functional component as shown below:
Functional Component
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
let stopms;
let stopSeconds;
let stopMinutes;
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
};
const changeStatus = () => {
return setTimeState((prevState) => {
return { ...prevState, status: !prevState.status };
});
};
const changeMs = () => {
return setTimeState((prevState) => {
if (prevState.ms === 99) {
return { ...prevState, ms: 0 };
} else {
return { ...prevState, ms: prevState.ms + 1 };
}
});
};
const changeSeconds = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, seconds: 0 };
} else {
return { ...prevState, seconds: prevState.seconds + 1 };
}
});
};
const changeMinutes = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, minutes: 0 };
} else {
return { ...prevState, minutes: prevState.minutes + 1 };
}
});
};
const handleReset = () => {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
setTimeState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
return (
<div>
<h1>
{timeState.minutes} : {timeState.seconds} . <span>{timeState.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={handleClick}>
{timeState.status === false ? "Start" : "Stop"}
</button>
<button className="btn btn-lg btn-dark" onClick={handleReset}>
Reset
</button>
</div>
);
}
export default Stopwatch;
The Problem
In the class component, I implemented the "Pause" functionality using the handleClick function which calls clearInterval with it's argument as the global variables stopms, stopSeconds, stopMinutes that I declared initially. This worked just fine because these global variables were holding values returned from the respective setInterval when the stop watch started counting.
Now in the functional component, I replicated the same logic by declaring the same global variables using the "let" keyword. But the "Pause" functionality is not working. When the "Start" button hit and the handleClick function called, the setIntervals were called and their return values were stored in the respective global variables. But when the "Pause" button was hit, all the global variables had "undefined" as their values.
Please I would like to know if there's any other way I can declare global variables and use them to hold values throughout a component's life cycle asides using state.
Functional components are executed from top to bottom whenever state changes, so the whole function is re-executed and that's how it returns the new JSX, compare this to class components where only render() function is executed on render, that's how functional components work.
The problem is that your global variables are in fact not global and a part of the function, hence they are re-initialized each time render is happening.
Two ways to solve this
Move your variables to the state
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
stopms : null,
stopSeconds : null,
stopMinutes: null,
});
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(timeState.stopms);
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
} else {
let stopms = setInterval(changeMs, 1);
let stopSeconds = setInterval(changeSeconds, 1000);
let stopMinutes = setInterval(changeMinutes, 60000);
setTimeState(prev => ({..prev, stopms, stopSeconds, stopMinutes})); // update the values in state
}
};
......
const handleReset = () => {
clearInterval(timeState.stopms); // use the same values to clear them
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
.....
};
.....
}
Or make them global by placing them outside of your component, Will work but not recommended.
In your component file.
// declare them just above your function
let stopms;
let stopSeconds;
let stopMinutes;
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
.....
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
.......
};
I created a stopwatch using react. My stopwatch starts from 0 and stops at the press of the space button with componenDidMount and componentWillMount. My issue is, I can't seem to figure out how to create some sort of list with the numbers the stopwatch returns. I've created:
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
and then in render() to print it.
<h1>{this.times}</h1>
What I'm trying to do is to create some sort of array that'll keep track of milliSecondsElapsed in my handleStart and handleStop method.
Here's what I have.
import React, {Component} from "react";
import Layout from '../components/MyLayout.js';
export default class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({})
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
{
timerInProgress: false
},
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
render() {
return (
<Layout>
<div className="index" align='center'>
<input
value={this.state.milliSecondsElapsed/100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button onClick={this.handleStart} ref={(ref) => (this.startBtn = ref)}>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed/100}</h1>
</div>
</Layout>
);
}
}
Issue
this.times is a function that only updates state, it doesn't return any renderable JSX.
times = () => {
this.setState((previousState) => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
Solution
Create a myArray state.
this.state = {
myArray: [], // <-- add initial empty array
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
Move the state update logic from this.times to this.handleStop.
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray, // <-- shallow copy existing data
this.state.milliSecondsElapsed / 100 // <-- add new time
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
Render the array of elapsed times as a comma separated list.
<div>{this.state.myArray.join(", ")}</div>
Full code
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
myArray: [],
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray,
this.state.milliSecondsElapsed / 100
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
render() {
return (
<div>
<div className="index" align="center">
<input
value={this.state.milliSecondsElapsed / 100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button
onClick={this.handleStart}
ref={(ref) => (this.startBtn = ref)}
>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed / 100}</h1>
</div>
<div>{this.state.myArray.join(", ")}</div>
</div>
);
}
}
I have a parent component which has timer component inside it. Timer starts at 15 minutes and count downs till 0. When my timer shows time as 0 I want to trigger a submit button event, submit button is inside Quiz Component (Quiz Component is also a child component of Parent Component). I found probably I can use MutationObserver when p tag changes. I am not sure whether it's the correct and only approach or there is better way to achieve this.
Parent Component:
import React, { Component } from 'react';
import '../css/App.css'
import Quiz from './Quiz';
import Timer from './Timer';
import { connect } from 'react-redux';
import { ActionTypes } from '../redux/constants/actionTypes';
import { saveQuizAll, getQuizIndex } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onQuizLoad: payload => dispatch({ type: ActionTypes.QuizLoad, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class QuizContainer extends Component {
state = {
quizes: [
{ id: 'data/class1.json', name: 'Class 1' },
{ id: 'data/class2.json', name: 'Class 2' },
{ id: 'data/class3.json', name: 'Class 3' },
{ id: 'data/class4.json', name: 'Class 4' },
],
quizId: 'data/class1.json'
};
pager = {
index: 0,
size: 1,
count: 1
}
componentDidMount() {
console.log('componentDidMount');
this.load(this.state.quizId);
}
load(quizId, isValReload) {
console.log('In load');
let url = quizId || this.props.quizId;
if (isValReload) {
let quiz = this.props.quizAll.find(a => url.indexOf(`${a.id}.`) !== -1);
console.log('In load quiz : ', quiz);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
}
else {
fetch(`../${url}`).then(res => res.json()).then(res => {
let quiz = res;
quiz.questions.forEach(q => {
q.options.forEach(o => o.selected = false);
});
quiz.config = Object.assign(this.props.quiz.config || {}, quiz.config);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
});
}
}
//This event implements restriction to change class without finishing curretnly selectd class
onClassClick = (e) => {
let qus = this.props.quiz.questions;
// console.log(qus);
let isNotAllAns = qus.some((q, i) => {
var isNot = false;
if (q.answerType.id !== 3 && q.answerType.id !== 4) {
isNot = (q.options.find((o) => o.selected === true)) === undefined;
}
else {
// console.log('q', q);
isNot = ((q.answers === "" || q.answers.length === 0));
}
return isNot;
});
if (isNotAllAns) {
alert('Please complete the quiz.');
e.stopPropagation();
}
}
/*
saveQuizAll(_quizAll, _quiz) {
let allQuiz = [];
// , _quizAll, _quiz;
// if (true) {
// _quiz = this.quiz;
// _quizAll = this.quizAll;
// }
console.log(this, _quiz, _quizAll);
if (_quiz.questions.length !== 0) {
if (_quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', _quiz.id);
allQuiz = _quizAll;
const qIndex = this.getQuizIndex(_quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, _quiz);
}
else {
allQuiz.splice(_quizAll.length, 0, _quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = _quiz;
}
return allQuiz;
// if (true) {
// this.onQuizChange(allQuiz);
// }
}
}
*/
onChange = (e) => {
// console.log(this.props.quizAll, this.props.quizAll.length);
let allQuiz = [];
allQuiz = saveQuizAll(this.props.quizAll, this.props.quiz);
//below code converted into saveQuizAll funstion
/*
if (this.props.quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', this.props.quiz.id);
allQuiz = this.props.quizAll;
const qIndex = this.getQuizIndex(this.props.quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, this.props.quiz);
}
else {
allQuiz.splice(this.props.quizAll.length, 0, this.props.quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = this.props.quiz;
}
*/
// console.log('allQuiz Out - ', allQuiz);
this.props.onQuizChange(allQuiz);
console.log('Check QuizAll - ', this.props.quizAll);
const aQuiz = JSON.parse(JSON.stringify(this.props.quizAll));
this.setState({ quizId: e.target.value });
if (aQuiz.length !== undefined && getQuizIndex(this.props.quizAll, e.target.value) > -1) {
// console.log(aQuiz.findIndex(a => e.target.value.indexOf(`${a.id}.`) !== -1));
this.load(e.target.value, true);
}
else {
this.setState({ quizId: e.target.value });
this.load(e.target.value, false);
}
}
// getQuizIndex(qID) {
// return this.props.quizAll.findIndex(a => (qID.indexOf(`${a.id}.`) !== -1 || qID.indexOf(`${a.id}`) !== -1));
// }
render() {
return (
<div className="container">
<header className="p-2">
<div className="row">
<div className="col-6">
<h3>DADt Application</h3>
</div>
<div className="col-6 text-right">
<label className="mr-1">Select Quiz:</label>
<select onChange={this.onChange} onClick={this.onClassClick}>
{this.state.quizes.map(q => <option key={q.id} value={q.id}>{q.name}</option>)}
</select>
</div>
</div>
</header>
<Timer duration={900}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QuizContainer);
Here is my Timer Component
import React, { Component } from 'react'
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
seconds: 0
};
}
tick() {
this.setState((prevState) => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { duration } = this.props;
let timeLeft = duration - this.state.seconds;
timeLeft = Number(timeLeft);
let minutes = Math.floor(timeLeft % 3600 / 60);
let seconds = Math.floor(timeLeft % 3600 % 60);
let minutesDisplay = minutes > 0 ? minutes + (minutes === 1 ? " : " : " : ") : "";
let secondsDisplay = seconds > 0 ? seconds + (seconds === 1 ? "" : "") : "";
return <p className="badge badge-success">Time Left: {minutesDisplay}{secondsDisplay}</p>;
}
}
export default Timer;
Quiz Component:
import React, { Component } from 'react';
import { ActionTypes } from '../redux/constants/actionTypes';
import Review from './Review';
import Questions from './Questions';
import Result from './Result';
import { connect } from 'react-redux';
// import { saveQuizAll } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.mode, ...state.pager, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onSubmit: payload => dispatch({ type: ActionTypes.QuizSubmit, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class Quiz extends Component {
move = (e) => {
let id = e.target.id;
let index = 0;
if (id === 'first')
index = 0;
else if (id === 'prev')
index = this.props.pager.index - 1;
else if (id === 'next') {
index = this.props.pager.index + 1;
}
else if (id === 'last')
index = this.props.pager.count - 1;
else
index = parseInt(e.target.id, 10);
if (index >= 0 && index < this.props.pager.count) {
let pager = {
index: index,
size: 1,
count: this.props.pager.count
};
this.props.onPagerUpdate(pager);
}
}
saveStore(e) {
let allQuiz = [];
console.log(this, e);
allQuiz = this.props.saveAll(e.props.quizAll, e.props.quiz);
console.log(allQuiz);
this.props.onQuizChange(allQuiz);
}
setMode = (e) => this.props.onSubmit(e.target.id);
// setMode(e) {
// console.log('in mode',e);this.props.onSubmit(e.target.id);
// }
renderMode() {
console.log('Inside here', this.props.mode);
if (this.props.mode === 'quiz') {
return (<Questions move={this.move} />)
} else if (this.props.mode === 'review') {
return (<Review quiz={this.props.quiz} move={this.move} />)
} else {
console.log('Before Results');
const divSel = document.querySelector('div.col-6.text-right');
// console.log('divSel', divSel);
if (divSel) {
divSel.style.display = "none";
}
return (<Result questions={this.props.quizAll || []} />)
}
}
render() {
return (
<div>
{this.renderMode()}
{(this.props.mode !== 'submit') &&
<div>
<hr />
<button id="quiz" className="btn btn-primary" onClick={this.setMode}>Quiz</button>
<button id="review" className="btn btn-primary" onClick={this.setMode}>Review</button>
<button id="submit" className="btn btn-primary" onClick={(e) => {this.setMode(e); this.saveStore(this)}}>Submit Quiz</button >
</div >}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Quiz);
I think you can have two approaches.
1. The "react" way
In the Parent component:
// ...
constructor(props) {
// ...
this.state = {
timeExpired: false
};
}
const onTimeExpired = () => {
this.setState({timeExpired: true});
}
// ...
render() {
return (
<div className="container">
{ // ... }
<Timer duration={900} onTimeExpired={onTimeExpired}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} triggerSubmit={this.state.timeExpired} />
</div>
);
}
In the Timer component:
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
this.props.onTimeExpired();
}
}
// ...
In the Quiz component:
// ...
componentDidUpdate() {
if (this.props.triggerSubmit) {
// Do whatever you do on submit
}
}
// ...
2. The "quick and dirty" way:
In the Timer component
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
const quizForm = document.getElementById('quizFormId');
quizForm && quizForm.submit();
}
}
// ...
Provide a prop method onTimeFinished in your Timer component. Then in your render function you can add
{ !(this.props.duration-this.state.seconds) && this.props.onTimeFinished() }
Reference: React Conditional Rendering
try this:
Parent Component:
// state
state = {
triggerSubmit: false
}
// functions
doSubmit = () => {
this.setState({ triggerSubmit: true });
}
resetSubmit = () => {
this.setState({ triggerSubmit: false });
}
// jsx
<Timer duration={900} doSubmit={this.doSubmit} />
<Quiz
quiz={this.state.quiz}
quizId={this.state.quizId}
saveAll={saveQuizAll}
mode={this.state.mode}
resetSubmit={this.resetSubmit}
triggerSubmit={this.state.triggerSubmit} />
Timer Component:
// function
doSubmit = (timeLeft) => {
if (timeLeft === 0) {
this.props.doSubmit();
}
}
// jsx
<p className="badge badge-success"
onChange={() => {this.doSubmit(timeLeft)}>
Time Left: {minutesDisplay}{secondsDisplay}
</p>
Quiz Component:
// state
state = {
triggerSubmit: this.props.triggerSubmit
}
// function
triggerSubmit = () => {
if (this.state.triggerSubmit) {
your trigger submit code here...
this.props.resetSubmit();
}
}
React Newbie here,
import React, { Component } from "react";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i].song_url));
this.buttonText.push(String(i));
}
this.state = {
songs: "",
buttonText: this.buttonText
};
}
componentWillMount() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText[i]);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
this.setState({
songs: songs
});
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
if (this.state.songs) {
return <div className="song-list">{this.state.songs}</div>;
} else {
return <div className="song-list"></div>;
}
}
}
export default AudioList;
I am using this code from a previous solution that I found on Stackoverflow (https://stackoverflow.com/a/50595639). I was able to implement this solution to solve my own challenge of needing to have multiple audio sources with one audio player and multiple buttons. However, I am now faced with a new challenge - I want a specific button's text to change when an event is fired up.
I came up with this implementation where the button text is based on an array in the state called buttonText. The buttons are rendered correctly on startup, but when the event listener picks up the event and changes the state, the text in the button is not re-rendering or changing, even though it is based on an element in an array in the state that is changing.
Does anyone have any suggestions about why it may be failing to re-render?
EDIT: Changing an individual array element in the state is based on React: how to update state.item[1] in state using setState?
I have restructured your code a bit (but it's untested it's tested now):
const songs = [
{
title: "small airplane long flyby - Mike_Koenig",
song_url: "http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3"
},
{
title: "Female Counts To Ten",
song_url: "http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3"
},
];
class AudioList extends React.Component {
audios = new Map();
state = {
audio: null,
song: null
};
componentDidUpdate() {
// stop playing if the song has been removed from props.songs
if (this.state.song && !this.props.songs.some(song => song.song_url === this.state.song.song_url)) {
this.toggle(this.state.audio, this.state.song);
}
}
componentWillUnmount() {
if (this.state.audio) {
this.state.audio.pause();
}
this.audios.clear();
}
toggle(audio, song) {
this.setState(state => {
if (audio !== state.audio) {
if (state.audio) {
state.audio.pause();
}
audio.play();
return { audio, song };
}
audio.pause();
return { audio: null, song: null };
});
}
getAudio(song) {
let audio = this.audios.get(song.song_url);
if (!audio) {
this.audios.set(song.song_url, audio = new Audio(song.song_url));
}
return audio;
}
render() {
return <div className="song-list">{
this.props.songs.map((song, i) => {
const audio = this.getAudio(song);
const playing = audio === this.state.audio;
return <div className="song-preview">
<button
className="preview"
onClick={this.toggle.bind(this, audio, song)}
>
{playing ? "playing" : (song.title || i)}
</button>
</div>
})
}</div>;
}
}
ReactDOM.render(<AudioList songs={songs} />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Edit: added a title to the song-objects and display them on the buttons
I simply moved all of the code from componentWillMount() to render(). I also removed 'songs' as a state variable and set it to a variable that exists only in render as songs is simply just a set of divs.
import React, { Component } from "react";
const audio1 =
"http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3";
const audio2 =
"http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i]));
this.buttonText.push(String(i));
}
this.state = {
buttonText: this.buttonText
};
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
console.log("playing");
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
return (
<div>{songs}</div>
)
}
}
export default () => <AudioList songs={[audio1, audio2]} />;
The code now runs as expected.
intervalID = setInterval(() => window.location.reload(true), 15000 );
I have tried above logic it is working fine but i need a logic in reactjs that refresh window page when user is idle
In case you are using class based components. you could make use of event listeners for tracking the user activity as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isIdle: false
}
this.idleTime = 0;
this.handleTimer = this.handleTimer.bind(this);
this.resetTimer = this.resetTimer.bind(this);
}
componentDidMount() {
document.documentElement.addEventListener('mousemove', this.resetTimer);
document.documentElement.addEventListener('keypress', this.resetTimer);
this.idleInterval = setInterval(this.handleTimer, 1000);
}
componentWillUnmount() {
document.documentElement.removeEventListener('mousemove', this.resetTimer);
document.documentElement.removeEventListener('keypress', this.resetTimer);
clearInterval(this.idleInterval)
}
resetTimer() {
this.idleTime = 0;
this.setState({isIdle: false})
}
handleTimer() {
this.idleTime = this.idleTime + 1;
if (this.idleTime > 10) {
this.handleIdle();
}
}
handleIdle() {
// Refresh your page here
this.setState({isIdle: true})
}
render() {
return(
<div>
<h1 className="demo">React Template</h1>
{this.state.isIdle &&
<p>You have been idle for {this.idleTime} seconds</p>
}
</div>
)
}
};
ReactDOM.render(
<App />,
document.getElementById("app")
);
try below the code, edit.
useEffect(() => {
const check = ()=> {
if (!document.hasFocus()) {
window.location.reload()
}
}
setInterval(check, 1000*60*15)
})
or one line.
useEffect(() => {
setInterval(()=> !document.hasFocus() ? document.location.reload(): '', 1000*60*15)
})
please let me know if you need this solution.