Bug in my react Stopwatch code. Issue with clearInterval()? - javascript

I've been trying to build a simple stopwatch application in React. The app is rendering okay but the stop and clear buttons are not working. Once started the timer will just keep on running.
Been banging my head on the keyboard for awhile now with this. Any help appreciated.
Thanks.
class Stopwatch extends Component {
state = {
status: false,
runningTime: 0
};
handleClick = () => {
this.setState(state => {
if (state.status) {
clearInterval(this.timer);
} else {
const startTime = Date.now() - this.state.runningTime;
this.timer = setInterval(() => {
this.setState({ runningTime: Date.now() - startTime });
});
}
return { status: !state.status };
});
};
handleReset = () => {
clearInterval(this.timer);
this.setState({ runningTime: 0, status: false });
};
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
const { status, runningTime } = this.state;
return (
<div>
<p>{runningTime}ms</p>
<button onClick={this.handleClick}>{status ? 'Stop' : 'Start'}</button>
<button onClick={this.handleReset}>Reset</button>
</div>
);
}
}

Related

Handling methods in order in React

Hi im trying to code a Timer. My problem is that the countdown should start after clicking a button and when that status is changed from start to stop and vise versa. I cant figure out how to handle it and which level to put it.
ADDITIONAL INFO:
When clicking the button, it goes to the method handler. Changes the status with setstate() but it renders at the end. Which is to late for the countdown to start.
Here is the Game Component:
import React, { Component } from 'react';
import './Game.css';
import Timer from './Timer'
class Game extends Component {
constructor() {
super();
}
state = {
buttonStatus: {status:"Start" , classButton:"Button ButtonBackgroundColorGrey" },
dotclass : "",
timer: 60
}
componentDidMount() {
this.timersignal();
}
buttonclick = () =>{
(this.state.buttonStatus.status === "Start")
? this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
: this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""})
this.componentDidMount();
}
timersignal = () => {
if(this.state.buttonStatus.status === "Stop") {
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
console.log("didMount start")
}
else(
console.log("didMount stop")
)
}
render() {
return (
<div>
<div className="Body-Container">
<h2 className="States"> Time </h2>
<Timer buttonstate= {this.state.timer}/>
<button className={this.state.buttonStatus.classButton} onClick={this.buttonclick}>{this.state.buttonStatus.status}</button>
</div>
</div>
);
}
}
export default Game;
Here is the Timer Component:
import React, { Component } from 'react';
import "./Timer.css";
class Timer extends Component {
render() {
return (
<h3 className="Timer">{this.props.buttonstate}</h3>
);
}
}
export default Timer ;
You just need one method and call it in componentDidMount and on click.
timerToggle = () =>{
if((this.state.buttonStatus.status === "Start") {
this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
clearInterval(this.Interval);
}else{
this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""}) ;
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
}
}
componentDidMount() {
this.timerToggle();
}
The Final Answer:
timerToggle = () =>{
if(this.state.buttonStatus.status === "Start") {
this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
}
else{
this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""}) ;
clearInterval(this.Interval);
}
}

Cannot set property 'myInterval' of undefined in React

Is anyone able to help me with this error that is appearing when the start timer function is called by a button component? I originally had it set up using componentDidMount so that the timer started on its own, but wanted to eventually implement it into a button click to start
Any other advice on how to clean up them if statements would be greatly appreciated
Cannot set property 'myInterval' of undefined
class Timer extends React.Component {
state = {
seconds: 5,
stage: "breathIn",
repetition: 4,
};
changeSeconds(x) {
const { y } = this.state;
this.setState(({ y }) => ({
seconds: x,
}));
}
changeStage(x) {
const { y } = this.state;
this.setState(({ y }) => ({
stage: x,
}));
}
changeRepetitions() {
const { repetition } = this.state;
this.setState(({ repetition }) => ({
repetition: repetition - 1,
}));
}
startTimer() {
this.myInterval = setInterval(() => {
const { seconds, stage, repetition } = this.state;
if (seconds > 0) {
this.setState(({ seconds }) => ({
seconds: seconds - 1,
}));
}
if (seconds === 0) {
if (repetition > 0) {
if (stage === "breathIn") {
this.changeSeconds(7);
this.changeStage("hold");
} else if (stage === "hold") {
this.changeSeconds(8);
this.changeStage("breathOut");
} else if (stage === "breathOut") {
if (repetition === 2) {
this.setState(({ repetition }) => ({
repetition: repetition - 1,
}));
this.changeSeconds(5);
this.changeStage("breathIn");
} else if (repetition === 1) {
this.changeSeconds(0);
this.changeStage("complete");
}
}
} else {
clearInterval(this.myInterval);
}
}
}, 1000);
}
componentWillUnmount() {
clearInterval(this.myInterval);
}
render() {
const { seconds, stage, repetition } = this.state;
return (
<div>
<Centerbutton startTimer={this.startTimer} />
<h1>{repetition}</h1>
<h1>{seconds}</h1>
</div>
);
}
}
export default Timer;
<Centerbutton startTimer={this.startTimer} />
should be either
<Centerbutton startTimer={this.startTimer.bind(this)} />
or
<Centerbutton startTimer={()=>this.startTimer()} />
It's a classic JS event binding problem
Just place your setInterval function inside componentDidMount life cycle method, follow the below example, you can't use the setInterval direct in react.
componentDidMount() {
this.interval = setInterval(() => {
// Your code here
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
Learn more about setInterval and Here is the demo app

ReactJS Stopwatch, how to handle localstorage data

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

React reusing a component with different state

I'm pretty new to React and trying to write my first app to get a better understanding.
What I'm trying to build is a simple time tracking tool where the user can start and stop a work timer.
Here you can see the design I came up with:
If the user clicks on the "start" button the working time Timer component should update every second. If the user clicks then on the "take a break" button the timer should stop and instead the break time Timer component should start ticking.
I would like to reuse the Timer component for both working and break timer and just set different states.
I already managed to do this but I don't know if this is a nice way or if this can be improved and make it more generic?
My Tracker component looks like this:
class Tracker extends Component {
constructor(props) {
super(props);
this.state = {
workTime: 0,
breakTime: 0,
isRunning: false,
timerType: 'workTimer'
}
}
startTimer(type) {
this.setState({
isRunning: true,
timerType: type
});
this.timerInterval = setInterval(() => {
this.updateTimer()
}, 1000);
}
stopTimer() {
this.setState({
isRunning: false
});
clearInterval(this.timerInterval);
}
toggleBreak(type) {
this.setState({
timerType: type
});
if (!this.state.isRunning && this.state.timerType === 'breakTimer') {
this.startTimer('breakTimer');
} else if (this.state.isRunning && this.state.timerType === 'breakTimer') {
this.stopTimer();
this.startTimer('workTimer');
} else {
this.stopTimer();
this.startTimer('breakTimer');
}
}
updateTimer() {
let state = null;
if (this.state.timerType === 'workTimer') {
state = {
workTime: this.state.workTime + 1000
};
} else {
state = {
breakTime: this.state.breakTime + 1000
};
}
this.setState(state);
}
render() {
return (
<div className="tracker">
<Timer time={ this.state.workTime }/>
<Timer time={ this.state.breakTime }/>
<TimerControls
isRunning={ this.state.isRunning }
start={ () => this.startTimer('workTimer') }
stop={ () => this.stopTimer() }
toggleBreak={ () => this.toggleBreak('breakTimer') }
/>
</div>
);
}
}
Controls component:
class TimerControls extends Component {
constructor(props) {
super(props);
}
render() {
const {isRunning, start, stop, toggleBreak} = this.props;
return (
<div className="tracker__control">
<button onClick={ start } disabled={ isRunning }>Start</button>
<button onClick={ toggleBreak }>Break</button>
<button onClick={ stop } disabled={ !isRunning }>Stop</button>
</div>
);
}
}
Timer component:
class Timer extends Component {
constructor(props) {
super(props);
}
render() {
const { time } = this.props;
return (
<div className="tracker__timer">{ timeFormat(time) }</div>
);
}
}
Is there a way to get rid of the timerType conditions?

Decrementing a number in React State

I'm trying to decrement my timeLeft property on my state by one every second. In the startRecording function I call the startTimer method but can't seem to get the state to decrease. What am I doing wrong? Thanks!
class NimbusCamera extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
navigator: PropTypes.object.isRequired
}
state = {
camera: {
aspect: Camera.constants.Aspect.fill,
captureTarget: Camera.constants.CaptureTarget.disk,
type: Camera.constants.Type.front,
captureMode: Camera.constants.CaptureMode.video,
captureAudio: true,
flashMode: Camera.constants.FlashMode.auto
},
isRecording: false,
timeLeft: 30,
reachedLimit: false
}
startTimer = () => {
console.log('Starting timer...')
let timerId = setInterval(countdown, 1000); // Run function every second
const countdown = () => {
console.log('Counting down...')
if (this.state.timeLeft === 0) {
clearTimeout(timerId);
this.setState({isRecording: false})
} else {
console.log(Decrementing...)
this.setState({timeLeft: this.state.timeLeft - 1});
}
}
}
render() {
console.log(this.state)
return (
<View style={styles.container}>
<Camera
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={this.state.camera.aspect}
type={this.state.camera.type}
captureAudio={this.state.camera.captureAudio}
flashMode={this.state.camera.flashMode}
>
<Text>{this.state.timeLeft}</Text>
<Text style={styles.capture} onPress={this.startRecording.bind(this)}>[CAPTURE]</Text>
<Text style={styles.capture} onPress={this.stopRecording.bind(this)}>[STOP]</Text>
</Camera>
</View>
);
}
startRecording = () => {
if (this.camera) {
this.camera.capture({mode: Camera.constants.CaptureMode.video})
.then((data) => this.props.dispatch(getPath(data.path)))
.catch(err => console.error(err));
this.startTimer();
this.setState({
isRecording: true
});
}
}
stopRecording = () => {
if (this.camera) {
this.camera.stopCapture();
this.setState({
isRecording: false
});
}
}
}
Incrementers like ++ and -- wont work. Anytime you want to update state based on a previous state value you should use this syntax.
this.setState((prevState) => ({ timeLeft: prevState.timeLeft - 1 });
Try putting your state object in a constructor:
constructor(props){
super(props);
this.state = {
camera: {
aspect: Camera.constants.Aspect.fill,
captureTarget: Camera.constants.CaptureTarget.disk,
type: Camera.constants.Type.front,
captureMode: Camera.constants.CaptureMode.video,
captureAudio: true,
flashMode: Camera.constants.FlashMode.auto
},
isRecording: false,
timeLeft: 30,
reachedLimit: false
}
Because -- executes after timeLeft in the object is assigned. It's not a reference, so the value setState gets is the one before decrementing.
var out = document.getElementById('out');
var outCorrect = document.getElementById('outCorrect');
function setState(newState) {
out.innerHTML = newState.timeLeft;
}
function setStateCorrect(newState) {
outCorrect.innerHTML = newState.timeLeft;
}
var timeLeft = 10;
setState({timeLeft: timeLeft--});
var timeLeftCorrect = 10;
setStateCorrect({timeLeft: timeLeftCorrect - 1});
<span id="out"></span>
<span id="outCorrect"></span>
Run the snippet and see why. a - b executes before the resulting value is assigned, a-- is executed after.
The simple way;
this.setState(state => {
return {
timeLeft: state.timeLeft - 1,
}
});

Categories

Resources