How to implement global variable in react functional components - javascript

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);
}
.......
};

Related

Why is the timer not working correctly when updating data in React?

My timer (MODAL -> componentDidUpdate -> timer) starts to work incorrectly after updating the data (MAIN -> componentDidMount -> timer) - it starts working faster and outputs -1 at the end. What's wrong? If you remove the data update (MAIN -> componentDidMount -> timer), the timer (MODAL -> componentDidUpdate -> timer) works correctly.
import React, { Component } from 'react';
class Main extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isOpened: false
};
this.submitForm = this.submitForm.bind(this);
}
componentWillMount() {
fetch('/api/global.json')
.then(response => response.json())
.then(result =>
this.setState({
data: result.data
// get data
}));
}
componentDidMount() {
this.timer = setInterval(() => this.componentWillMount(), 10000);
}
componentWillUnmount() {
this.timer = null;
}
changeModal = () => {
this.setState({ isOpened: !this.state.isOpened });
}
submitForm(e) {
e.preventDefault();
this.setState({ isOpened: !this.state.isOpened });
}
render() {
return (
<div>
<form onSubmit={this.submitForm}>
<button className="button button-main" type="submit">Modal open</button>
</form>
<Modal isOpened={this.state.isOpened} changeModal={() => this.changeModal(this)} />
</div>
);
}
}
class Modal extends Component {
constructor(props) {
super(props);
this.state = { counter: 30 };
}
componentDidUpdate(prevProps) {
if (prevProps.isOpened !== this.props.isOpened) {
if (!this.props.isOpened) {
this.setState({ counter: 30 });
clearTimeout(this.timer);
}
}
if (this.props.isOpened && this.state.counter > 0) {
this.timer = setTimeout(() => this.setState({ counter: this.state.counter - 1 }), 1000);
}
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
const padTime = time => {
return String(time).length === 1 ? `0${time}` : `${time}`;
};
const format = time => {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
return `${minutes} мин ${padTime(seconds)} сек`;
};
return (
<div className={"modal-overlay" + (this.props.isOpened ? " open" : " close")}>
<div className='modal-content'>
<div className='modal-close' onClick={() => this.props.changeModal()}>×</div>
<div className="modal-note">{this.state.counter === 0 ? 'Finish!' : 'Timer ' + format(this.state.counter)}</div>
</div>
</div>
)
}
}
export default Main;
While I'm waiting for the best solution, I did the following and it works - the counter in the modal window does not speed up after updating the main component. But I'm not sure if this is the correct solution. I look forward to your help.
class Modal extends Component {
constructor(props) {
super(props);
this.state = { counter: 30 };
}
componentDidMount() {
if (this.state.counter > 0) {
this.timer = setInterval(() => this.setState({ counter: this.state.counter > 0 ? this.state.counter - 1 : 0}), 1000);
}
}
componentDidUpdate(prevProps) {
if (prevProps.isOpened !== this.props.isOpened) {
if (this.props.isOpened) {
this.setState({ counter: 30 });
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
const padTime = time => {
return String(time).length === 1 ? `0${time}` : `${time}`;
};
const format = time => {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
return `${minutes} мин ${padTime(seconds)} сек`;
};
return (
<div className={"modal-overlay" + (this.props.isOpened ? " open" : " close")}>
<div className='modal-content'>
<div className='modal-close' onClick={() => this.props.changeModal()}>×</div>
<div className="modal-note">{this.state.counter === 0 ? 'Finish!' : 'Timer ' + format(this.state.counter)}</div>
</div>
</div>
)
}
}

I have a stopwatch in reactjs, how can I add each number into some sort of array to show each number?

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>
);
}
}

How to trigger event when timer count downs to 0

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();
}
}

Calling the 'stop' function that stops the clock by clicking <li> </ li>. Passing functions 'stop' between components

Expected effect:
Click Name A. Name A is active
When I click on the 'Name B' element, I want to call the 'stopTimer ()' function. Stop the time in the element 'Name A' and run function 'stopTimer()'
When I click an element such as 'Name A', name A is active, I can not call the 'stopTimer ()' function until I click 'Name B'
And vice versa when item 'Name B' is active. Click item 'Name A' call function 'stopTimer ()'
Is this solution possible at all? I am asking for advice.
Updated:
All code: https://stackblitz.com/edit/react-kxuvgn
I understood my mistake. I should put the function stopTimer () in the parent App. The function stopTimer is needed both in theApp, when I click to stop the clock, as well as in the Stopwatch component plugged into thestop button. Where should I set this: {timerOn: false}); clearInterval (this.timer);That it was common for both components?
stopTimer = () => {
     this.setState ({timerOn: false});
     clearInterval (this.timer);
};
App
class App extends React.Component {
constructor() {
super();
this.state = {
selectedTodoId: '',
selectedTabId: null,
items: [
{
id: 1,
name: 'A',
description: 'Hello'
},
{
id: 2,
name: 'B',
description: 'World'
}
],
selectIndex: null
};
}
stopTimer = (timer, timerOn) => {
this.setState({ timerOn: timerOn });
clearInterval(timer);
};
select = (id) => {
if(id !== this.state.selectedTabId){
this.setState({
selectedTodoId: id,
selectedTabId: id
})
this.stopTimer();
}
}
isActive = (id) => {
return this.state.selectedTabId === id;
}
render() {
return (
<div>
<ul>
{
this.state.items
.map((item, index) =>
<Item
key={index}
index={index}
item={item}
select={this.select}
items = {this.state.items}
selectIndex = {this.state.selectIndex}
isActive= {this.isActive(item.id)}
/>
)
}
</ul>
<ItemDetails
items = {this.state.items}
selectIndex = {this.state.selectIndex}
resul={this.state.resul}
/>
<Stopwatch
stopTimer = {this.stopTimer}
/>
</div>
);
}
}
Watch
class Stopwatch extends Component {
constructor() {
super();
this.state = {
timerOn: false,
timerStart: 0,
timerTime: 0
};
}
startTimer = () => {
this.setState({
timerOn: true,
timerTime: this.state.timerTime,
timerStart: Date.now() - this.state.timerTime
});
this.timer = setInterval(() => {
this.setState({
timerTime: Date.now() - this.state.timerStart
});
}, 10);
};
stopTimer = () => {
this.setState({ timerOn: false });
clearInterval(this.timer);
};
resetTimer = () => {
this.setState({
timerStart: 0,
timerTime: 0
});
};
render() {
const { timerTime } = this.state;
let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);
return (
<div>
<div className="Stopwatch-display">
{hours} : {minutes} : {seconds}
</div>
{ (
<button onClick={this.startTimer}>Start</button>
)}
{(
<button onClick={this.stopTimer}>Stop</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.resetTimer}>Reset</button>
)}
</div>
);
}
}
You have to lift your state up for this, before the child unmounts, pass the value to
parent component to store.
componentWillUnmount() {
this.stopTimer();
this.props.updateTimerTime(this.props.index, this.state.timerTime);
}
When the child component mounts set the state from props passed from parent component.
componentDidMount() {
this.setState({
timerTime: this.props.timerTime,
});
}
Stackblitz demo

React Component Didn't Mount

I have got problem with my react component which renders form. Basically, when I enter countdown page, the form just doesn't work (by that I mean it doesnt act at all, I write for example 123 which is 2 min and 3 seconds and nothing happens, just nothing). But, for example, if I go on to main page and back to countdown page, it works. I have noticed that when entering this page the first time, componentWillMount works, but componentDidMount doesn't (it won't console.log the message).
Link to heroku: http://fathomless-lowlands-79063.herokuapp.com/?#/countdown?_k=mj1on6
CountdownForm.jsx
var React = require('react');
var CountdownForm = React.createClass({
onSubmit: function (e) {
e.preventDefault();
var strSeconds = this.refs.seconds.value;
if (strSeconds.match(/^[0-9]*$/)){
this.refs.seconds.value = '';
this.props.onSetCountdown(parseInt(strSeconds, 10));
}
},
render: function () {
return(
<div>
<form ref="form" onSubmit={this.onSubmit} className="countdown-form">
<input type="text" placeholder="Enter time in seconds" ref="seconds" />
<button className="button expanded">Start
</button>
</form>
</div>
);
}
});
module.exports = CountdownForm;
Countdown.jsx
var React = require('react');
var Clock = require('Clock');
var CountdownForm = require('CountdownForm');
var Controls = require('Controls');
var Countdown = React.createClass({
getInitialState: function () {
return {
count: 0,
countdownStatus: 'stopped'
};
},
componentDidUpdate: function (prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus)
{
switch (this.state.countdownStatus){
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count: 0})
case 'paused':
clearInterval(this.timer)
this.timer = undefined;
break;
}
}
},
componentDidMount: function() {
console.log("componentDidMount");
},
componentWillMount: function () {
console.log("componentWillMount");
},
componentWillUnmount: function () {
console.log('componentDidUnmount');
},
startTimer: function () {
this.timer = setInterval(() => {
var newCount = this.state.count - 1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
}, 1000);
},
handleSetCountdown: function (seconds){
this.setState({
count: seconds,
countdownStatus: 'started'
});
},
handleStatusChange: function (newStatus) {
this.setState({
countdownStatus: newStatus
});
},
render: function () {
var {count, countdownStatus} = this.state;
var renderControlArea = () => {
if (countdownStatus !== 'stopped') {
return <Controls countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange} />
} else {
return <CountdownForm onSetCountdown={this.handleSetCountdown} />
}
};
return(
<div>
<Clock totalSeconds={count} />
{renderControlArea()}
</div>
);
}
});
module.exports = Countdown;
I have solved the problem. Main issue was error: "Uncaught Error: Stateless function components cannot have refs.
at invariant" . The problem was with stateless components, so that's why I instead of using arrow functions in Main.jsx and Nav.jsx, I used the React.createClass({}).

Categories