I'm setting an interval on componentDidMount() and saving it on my App state.
The timer starts correctly, but I don't achieve to clear it when I want. This is the initial state:
this.initialState = {
score: 0,
finished: false,
currentQuestion: 0,
questions: [],
minutes: 1,
seconds: 15,
timer: 0
};
The action looks like this. It receives a timer (interval) as a parameter:
export function updateTimer(timer) {
return {
type: UPDATETIMER,
payload: {
timer
}
}
}
I have this action creator function on my reducers.js:
function timer(state = 0, action = {}) {
switch(action.type) {
case RESTART:
return 0;
case UPDATETIMER:
if(action.payload.timer !== undefined){
return action.payload.timer;
}else {
clearInterval(state);
return 0;
}
default:
return state;
}
}
In App.js I use it like this:
<Navbar
onUpdateTimer= {
(timer) => {
this.props.dispatch(updateTimer(timer))
}
}
/>
What I'm tryng to do is that if I the call has an argument it sets the interval, and if it doesn't it clears it.
The component that calls the action is this one:
import React from 'react';
export default class CountDown extends React.Component {
constructor(props) {
super(props);
this.startTimer = this.startTimer.bind(this);
this.countDown = this.countDown.bind(this);
}
startTimer() {
this.props.onUpdateTimer(setInterval(this.countDown, 1000));
}
countDown() {
let minutes = this.props.minutes;
let seconds = this.props.seconds - 1;
if (seconds === 0) {
if(minutes !== 0){
minutes -=1;
seconds = 59;
}else {
this.props.timeUp()
setTimeout(function(){
alert("SE HA ACABADO EL TIEMPO");
},0);
}
}
this.props.onUpdateTime(minutes, seconds);
}
render() {
return (
<div id="clockdiv">
<span className="minutes">{this.props.minutes} : </span>
<span className="seconds">{this.props.seconds}</span>
</div>
);
}
componentDidMount(){
this.startTimer();
}
}
Am I using correctly clearInterval(state) in the action creator function?
Related
Edit: This problem has been solved by adding a second parameter in the useEffect Hooks.
I am new to react and am building a toy react game. I borrowed this timer I found here. If user performs an action and the parent component re-renders, the timer stop counting down for a short period of time. According to (https://reactjs.org/docs/hooks-effect.html), react will remember the useEffect function, and call it later after performing the DOM updates. If that's the reason, are there any solutions or alternatives to this? If not, can anyone point me in the right direction? Any help will be appreciated!
Here is my code snippet for reference:
class Chalkboard extends Component {
constructor(props) {
super(props);
this.state = {
minutes: 1,
seconds: 0,
addSeconds: 0,
};
}
updateTimer() {
this.setState({ addSeconds: 0 });
}
handleTimer() {
window.removeEventListener('keydown', this.keyHandling);
this.setState({
gameOver: true,
});
}
render() {
return(
<Timer
initialMinutes={this.state.minutes}
initialSeconds={this.state.seconds}
onTimer={this.handleTimer}
addSeconds={this.state.addSeconds}
updateTimer={this.updateTimer}
/>
)
}
import React, { useState, useEffect } from 'react';
const Timer = (props) => {
const { initialMinutes = 0, initialSeconds = 0 } = props;
const [minutes, setMinutes] = useState(initialMinutes);
const [seconds, setSeconds] = useState(initialSeconds);
useEffect(() => {
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
else if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval);
} else {
setSeconds(59);
setMinutes(minutes - 1);
}
}
}, 1000);
return () => {
clearInterval(myInterval);
};
});
useEffect(() =>{
if(props.addSeconds>0){
setSeconds(seconds + props.addSeconds);
props.updateTimer()
}
if(minutes === 0 && seconds === 0){
props.onTimer()
}
});
return (
<div className = "timer-container" >
{minutes === 0 && seconds === 0 ? null: (
<div>
{' '}
{minutes}:{seconds < 10 ? `0${seconds}` : seconds}
</div>
)}
</div>
);
};
export default Timer;
Can you provide an example on https://codesandbox.io/?
Try to pass [props, minutes, seconds] as a second parameter in useEffect hook.
https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
I'm working on an application and I have a timer with the current date, hour, minutes and seconds displayed. It is incremented every second. What I want to do is to stop the timer on a button click, but I'm not sure why the clearInterval() function doesn't work. My code is:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
timer: "",
};
}
componentDidMount() {
//current time
setInterval(() => {
this.setState({
currentTime : new Date().toLocaleString()
})
}, 1000)
}
stopTimer = () => {
clearInterval(this.state.currentTime)
}
render() {
return (
<div>
<h4>Current time: {this.state.currentTime}</h4>
<Button onClick={this.stopTimer}>Stop timer</Button>
</div>
)
}
}
setInterval() result is the intervalID and you should use this for clearInterval().
this.state = {
...
intervalId: ""
};
...
const intervalId = setInterval(() => {
...
}, 1000);
this.setState({ intervalId });
...
clearInterval(this.state.intervalId);
You are using the clearInterval on wrong variable . You should do this.
I'm working on an application and I have a timer with the current date, hour, minutes and seconds displayed. It is incremented every second. What I want to do is to stop the timer on a button click, but I'm not sure why the clearInterval() function doesn't work. My code is:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
timer: "",
intervalId: ""
};
}
componentDidMount() {
//current time
const intervalId = setInterval(() => {
this.setState({
currentTime : new Date().toLocaleString()
})
}, 1000);
this.setState({intervalId})
}
stopTimer = () => {
if (this.state.intervalId) {
clearInterval(this.state.intervelId)
}
}
render() {
return (
<div>
<h4>Current time: {this.state.currentTime}</h4>
<Button onClick={this.stopTimer}>Stop timer</Button>
</div>
)
}
}
i have component where i count time (Stopwatch). everything is working fine. Start, stop of the clock reset. I wanted to add functionality that when i stop clock (handleTimerStop) set current state to localstorage in case if i close browser and want to return and want to start where i left clock paused. So when i stop clock items are setup to localstorage but when i want to restart clock it doesn't take data from local storage but start from scratch. Could you please help? also it will be great if someone can optimsie my code becasue i feel it can be done better.
thanks
import React, { Component } from "react";
export default class Timer extends Component {
constructor(props) {
super(props);
this.state = {
timerStarted: false,
timerStopped: true,
hours: 0,
minutes: 0,
seconds: 0
};
}
componentDidMount() {
if (!localStorage.getItem("sec") === null) {
this.setState({
seconds: localStorage.getItem("sec")
});
}
if (!localStorage.getItem("min") === null) {
this.setState({
seconds: localStorage.getItem("min")
});
}
if (!localStorage.getItem("hour") === null) {
this.setState({
seconds: localStorage.getItem("hours")
});
}
}
handleTimerStart = e => {
e.preventDefault();
if (localStorage.getItem("sec") === null) {
this.setState({
seconds: 0
});
} else {
this.setState({
seconds: localStorage.getItem("sec")
});
}
if (localStorage.getItem("min") === null) {
this.setState({
minutes: 0
});
} else {
this.setState({
minutes: localStorage.getItem("min")
});
}
if (localStorage.getItem("hour") === null) {
this.setState({
hours: 0
});
} else {
this.setState({
hours: localStorage.getItem("hour")
});
}
if (this.state.timerStopped) {
this.timer = setInterval(() => {
this.setState({ timerStarted: true, timerStopped: false });
if (this.state.timerStarted) {
if (this.state.seconds >= 60) {
this.setState(prevState => ({
minutes: prevState.minutes + 1,
seconds: localStorage.getItem("sec")
}));
}
if (this.state.minutes >= 60) {
this.setState(prevState => ({
hours: prevState.hours + 1,
minutes: localStorage.getItem("min"),
seconds: localStorage.getItem("sec")
}));
}
this.setState(prevState => ({ seconds: prevState.seconds + 1 }));
}
}, 1000);
}
};
handleTimerStop = () => {
this.setState({ timerStarted: false, timerStopped: true });
clearInterval(this.timer);
localStorage.setItem("sec", this.state.seconds);
localStorage.setItem("min", this.state.minutes);
localStorage.setItem("hour", this.state.hours);
};
handelResetTimer = () => {
this.setState({
timerStarted: false,
timerStopped: true,
hours: 0,
minutes: 0,
seconds: 0
});
};
render() {
return (
<div className="container">
<h2 className="text-center"> React Based Timer </h2>
<div className="timer-container">
<div className="current-timer">
{this.state.hours +
":" +
this.state.minutes +
":" +
this.state.seconds}
</div>
<div className="timer-controls">
<button className="btn btn-success" onClick={this.handleTimerStart}>
Start Timer
</button>
<button className="btn btn-alert" onClick={this.handleTimerStop}>
Stop Timer
</button>
<button className="btn btn-info"> Capture Time </button>
<button className="btn btn-danger" onClick={this.handelResetTimer}>
{" "}
Reset!{" "}
</button>
</div>
</div>
</div>
);
}
}
These comparations are your problem.
!null === null or !'23' === null ... false always.
if (!localStorage.getItem("sec") === null) {
this.setState({
seconds: localStorage.getItem("sec")
});
}
if (!localStorage.getItem("min") === null) {
this.setState({
seconds: localStorage.getItem("min")
});
}
if (!localStorage.getItem("hour") === null) {
this.setState({
seconds: localStorage.getItem("hours")
});
}
This ain't an answer to your concrete question, but imo. a better approach at your code
This implementation is
independant of the render interval,
only writes to localStorage when you start/stop/reset it
and is not getting out of sync (even if it does not get rendered at all)
.
import React, { Component } from "react";
export default class Timer extends Component {
constructor(props) {
super(props);
try {
this.state = JSON.parse(localStorage.getItem(this.props.localStorage));
} catch (error) {}
if (!this.state) {
this.state = this.saveChanges({
running: false,
value: 0
});
}
}
componentWillMount() {
if (this.state.running) {
this.timer = setInterval(
() => this.forceUpdate(),
this.props.interval | 0
);
}
}
componentWillUnmount() {
if (this.state.running) {
clearInterval(this.timer);
}
}
saveChanges(state) {
console.log("saveChanges", this.props.localStorage, state);
if (this.props.localStorage) {
localStorage.setItem(this.props.localStorage, JSON.stringify(state));
}
return state;
}
start = () => {
const now = Date.now();
this.setState(({ running, value }) => {
if (running) return null;
this.timer = setInterval(
() => this.forceUpdate(),
this.props.interval | 0
);
return this.saveChanges({
running: true,
value: value - now
});
});
};
stop = () => {
const now = Date.now();
this.setState(({ running, value }) => {
if (!running) return null;
clearInterval(this.timer);
return this.saveChanges({
running: false,
value: value + now
});
});
};
reset = () => {
const now = Date.now();
this.setState(({ running, value }) => {
return this.saveChanges({
running: false,
value: 0
});
//just reset the timer to 0, don' stop it
//return this.saveChanges({
// running,
// value: running? -now: 0
//});
});
};
render() {
const {
start, stop, reset,
state: { running, value }
} = this;
const timestamp = running ? Date.now() + value : value;
const h = Math.floor(timestamp / 3600000);
const m = Math.floor(timestamp / 60000) % 60;
const s = Math.floor(timestamp / 1000) % 60;
const ms = timestamp % 1000;
const _ = (nr, length = 2, padding = 0) =>
String(nr).padStart(length, padding);
return (
<div className="container">
<h2 className="text-center"> React Based Timer </h2>
<div className="timer-container">
<div className="current-timer">
{_(h) + ":" + _(m) + ":" + _(s) + "." + _(ms, 3)}
</div>
<div className="timer-controls">
<button className="btn btn-success" onClick={start}>
Start Timer
</button>
<button className="btn btn-alert" onClick={stop}>
Stop Timer
</button>
<button className="btn btn-danger" onClick={reset}>
Reset!
</button>
</div>
</div>
</div>
);
}
}
check this Sandbox out
I can get a static elapsed seconds to display based on the pulled start time but I can't get the elapsed seconds to then continue counting. I know I probably shouldn't be using the props to update a state in the child but even without that I couldn't get it to work and tried that as a workaround. Any help is appreciated.
Parent looks like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
elapsedSeconds: -1
};
this.changeElapsedSecondsParent = this.changeElapsedSecondsParent.bind(this);
}
changeElapsedSecondsParent(newElapsed) {
this.setState({
elapsedSeconds: newElapsed
});
}
render() {
const { elapsedSeconds } = this.state;
return (
<div>
<Stopwatch
elapsedSeconds={elapsedSeconds}
changeElapsed={this.changeElapsedSecondsParent}
/>
</div>
);
}
}
Child stopwatch looks like this:
const formattedSeconds = (sec) =>
~~(sec / 60) +
':' +
('0' + sec % 60).slice(-2)
export class Stopwatch extends React.Component {
constructor(props) {
super(props);
this.state = {
secondsElapsed: -1,
laps: [],
lastClearedIncrementer: null
};
this.incrementer = null;
this.handleStartClick = this.handleStartClick.bind(this);
this.handleStopClick = this.handleStopClick.bind(this);
this.handleResetClick = this.handleResetClick.bind(this);
this.handleLapClick = this.handleLapClick.bind(this);
}
componentDidMount() {
if(this.state.secondsElapsed < 0) {
const getElapsedTime = async () => {
try{
const response = await fetch(`api url`);
if(response.ok){
let jsonResponse = await response.json();
/* start getting seconds elapsed since start */
let currentServerTime = new Date(jsonResponse[0].currentTimeStamp).getTime() /1000;
let currentD = Math.floor(new Date().getTime()/1000);
let deltaDate = (currentServerTime-currentD);
let raceStartTime = new Date(jsonResponse[0].startTime).getTime()/1000 - deltaDate;
let secondsElapsedSinceStart = currentD - raceStartTime;
/* end getting seconds elapsed since start */
this.props.changeElapsed(secondsElapsedSinceStart);
}
}
catch(error){
console.log(error);
}
}
getElapsedTime();
let newElapsed = this.props.elapsedSeconds;
this.incrementer = setInterval( () =>
this.setState({
secondsElapsed: newElapsed + 1
})
, 1000);
} else {
this.incrementer = setInterval( () =>
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
})
, 1000);
}
}
handleStartClick() {
/* start post request */
const pushTime = async () => {
try {
const response = await fetch('apiurl', {
method: 'POST',
body: JSON.stringify({"startTime": "'2018-08-26 16:57:09'"})
})
if(response.ok){
const jsonResponse = await response.json();
return jsonResponse;
}
throw new Error('Request failed!');
} catch(error) {
console.log(error);
}
}
pushTime();
}
handleStopClick() {
clearInterval(this.incrementer);
this.setState({
lastClearedIncrementer: this.incrementer
});
}
handleResetClick() {
clearInterval(this.incrementer);
this.setState({
secondsElapsed: 0,
laps: []
});
}
handleLapClick() {
this.setState({
laps: this.state.laps.concat([this.props.elapsedSeconds])
})
}
render() {
return (
<div className="stopwatch">
<h1 className="stopwatch-timer">{formattedSeconds(this.state.secondsElapsed)}</h1>
{(this.props.elapsedSeconds === 0 ||
this.incrementer === this.state.lastClearedIncrementer
? <Button className="start-btn" onClick={this.handleStartClick.bind(this)}>start</Button>
: <Button className="stop-btn" onClick={this.handleStopClick.bind(this)}>stop</Button>
)}
{(this.props.elapsedSeconds !== 0 &&
this.incrementer !== this.state.lastClearedIncrementer
? <Button onClick={this.handleLapClick.bind(this)}>lap</Button>
: null
)}
{(this.props.elapsedSeconds !== 0 &&
this.incrementer === this.state.lastClearedIncrementer
? <Button onClick={this.handleResetClick.bind(this)}>reset</Button>
: null
)}
<ul className="stopwatch-laps">
{ this.state.laps.map((lap, i) =>
<li className="stopwatch-lap"><strong>{i + 1}</strong>/ {formattedSeconds(lap)}</li>)
}
</ul>
</div>
);
}
}
const Button = (props) =>
<button type="button" {...props} className={"btn " + props.className } />;
I found it easier to define a constant in the constructor like:
this.deltaTime = null;
Then I created a changeDeltaTime function:
changeDeltaTime(newTime) {
this.deltaTime = newTime
}
lastly, I updated that deltaTime inside the promiseand after the response within the componentDidUpdate() lifecycle method.
await this.changeDeltaTime(deltaDate);
In render, I waited the promise to be resolved through a prop passed by parent
return !this.props.finishedGet ? <span>Waiting on fetch...</span> : (this.renderStopwatch(this.state.time))
This also involves the renderStopwatch function to be created but it basically just houses what should be created once the promise resolves.
How does one show a counter going from 1 to 2 to 3 to n on the click of a button. I've tried doing a setState in a for loop but thats not worked.
I know react's setState is async, i've even tried to use prevState, but its not worked.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.startCounter = this.startCounter.bind(this);
}
startCounter() {
const self = this;
for (let i = 0; i < 100; i++) {
this.setState(prevState => {
const counter = prevState.counter + 1;
return Object.assign({}, prevState, {counter: counter})
});
}
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
)
}
}
export default App;
webpack bin below
https://www.webpackbin.com/bins/-KkU1NJA-ectflyDgf_S
I want to increase the count from 0 to n as a timer of sorts when clicked.
Something like this?
When you run the startCounter() function, you start the interval which increments the counter value by 1, each second. Once it reaches n (5 in this example), it resets.
class App extends React.Component {
constructor() {
super();
this.interval;
this.state = {
counter: 1,
n: 5
};
}
startCounter = () => {
if (this.interval) return; //if the timer is already running, do nothing.
this.interval = setInterval(() => {
let c = (this.state.counter % this.state.n) + 1;
this.setState({
counter: c
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval); //remove the interval if the component is unmounted.
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>