Im creating a clock that has minutes and seconds. This is how my component looks like
class Time extends React.PureComponent
{
constructor(props)
{
super(props);
this.state =
{
hrs: 0,
mins: 0,
secs: 0
}
this.tick = this.tick.bind(this);
}
tick()
{
//This increments the minutes
if(this.state.secs >= 59)
{
this.setState({mins: this.state.mins+1});
this.setState({secs: 0});
}
//This increments the hours
if(this.state.minutes >= 59)
{
this.setState({hrs: this.state.hrs+1});
this.setState({mins: 0});
}
else
{
//This increments the seconds
this.setState({secs: this.state.secs+1});
}
}
componentDidMount()
{
this.tock = setInterval(()=>
{
this.tick();
}, 1000)
}
componentWillUnmount()
{
clearInterval(this.tock);
}
render()
{
return(
<div>
<h1>Time</h1>
<section>
{this.state.hrs}:{this.state.mins}:{this.state.secs}
</section>
</div>
);
}
}
}
export default Time;
The mins do increment to 59 and in turn increment the hrs - then reset to 0. But the secs just keep going and never reset to 0. They do update the mins though. The problem is that they go from 0 to infinity and dont stop counting. How can I make them reset to 0 each time?
setState is asynchronous, so you cannot ensure all the state updates in the sequence with multiple setState. I'd suggest that you should have only one setState at the end of that function after all secs, mins, and hrs computed.
Side note I also fixed your wrong value in this.state.minutes to this.state.mins.
class Time extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
hrs: 0,
mins: 0,
secs: 0,
};
this.tick = this.tick.bind(this);
}
tick() {
let { secs, mins, hrs } = this.state;
secs += 1;
if (secs > 59) {
mins += 1;
secs = 0;
}
if (mins > 59) {
hrs += 1;
mins = 0;
}
this.setState({ secs, mins, hrs });
}
componentDidMount() {
this.tock = setInterval(() => {
this.tick();
}, 1000);
}
componentWillUnmount() {
clearInterval(this.tock);
}
render() {
return (
<div>
<h1>Time</h1>
<section>
{this.state.hrs}:{this.state.mins}:{this.state.secs}
</section>
</div>
);
}
}
You could also use a functional based component to achieve the same effect.
function Time() {
const [time, setTime] = useState({
secs: 50,
mins: 58,
hrs: 0
});
const tick = () => {
setTime(prevState => ({...prevState, secs: prevState.secs + 1}));
}
useEffect(() => {
setInterval(() => {
tick()
}, 1000)
}, []);
useEffect(() => {
if(time.secs > 59) {
setTime(prevState => ({ ...prevState, secs: 0, mins: prevState.mins + 1 }));
}
}, [time.secs])
useEffect(() => {
if (time.mins > 59) {
setTime(prevState => ({ ...prevState, mins: 0, hrs: prevState.hrs + 1 }));
}
}, [time.mins])
return (
<div>
<h1>Time</h1>
<section>
{time.hrs}:{time.mins}:{time.secs}
</section>
</div>
);
}
export default Time;
Related
I am using a countdown component as a child component.
I want to disable/reable a button based on the state value of the counter, but I can't seem to read the value correctly.
This is what I have tried.
This is the countdown component:
import React from "react";
import PropTypes from "prop-types";
export default class Counter extends React.Component {
constructor() {
super();
this.state = { time: {}, seconds: 15 };
this.timer = 0;
this.startTimer = this.startTimer.bind(this);
this.countDown = this.countDown.bind(this);
}
secondsToTime(secs){
let hours = Math.floor(secs / (60 * 60));
let divisor_for_minutes = secs % (60 * 60);
let minutes = Math.floor(divisor_for_minutes / 60);
let divisor_for_seconds = divisor_for_minutes % 60;
let seconds = Math.ceil(divisor_for_seconds);
let obj = {
"h": hours,
"m": minutes,
"s": seconds
};
return obj;
}
componentDidMount() {
let timeLeftVar = this.secondsToTime(this.state.seconds);
this.setState({ time: timeLeftVar });
}
startTimer() {
if (this.timer === 0 && this.state.seconds > 0) {
this.timer = setInterval(this.countDown, 1000);
} else if ((this.timer === 0 && this.state.seconds === 0)){
this.state.seconds = 15;
this.timer = setInterval(this.countDown, 1000);
}
}
countDown() {
// Remove one second, set state so a re-render happens.
let seconds = this.state.seconds - 1;
this.setState({
time: this.secondsToTime(seconds),
seconds: seconds,
});
// Check if we're at zero.
if (seconds === 0) {
clearInterval(this.timer);
this.timer = 0;
console.log("counter is 0");
console.log(this.state.seconds);
console.log(this.timer);
}
}
render() {
this.startTimer();
return(
<span className={
this.state.seconds === 0 ? 'timerHidden' : 'timerActive'
}>
({this.state.time.s})
</span>
);
}
}
And how I read it and reset it in the parent component:
import Counter from '../Counter/Counter.js';
export default class Verify extends React.Component {
state = {
username: this.username,
email: this.email,
code: ""
};
constructor(props) {
super(props);
this.child = React.createRef();
}
resetTimer = () => {
this.child.current.startTimer();
};
resendConfirmationCode = async e =>{
this.resetTimer();
...
}
return (
<button
className="btn btn-primary register empty"
type="button"
disabled={this.child.current.seconds > 0}
onClick={this.resendConfirmationCode}>Resend code <Counter ref={this.child}/>
</button>
);
Inserting the counter works fine, reseting also, but the disabling of the button throws the following error:
TypeError: Cannot read property 'seconds' of null
Verify.render
> 109 | disabled={this.child.current.seconds > 0}
The this.child ref will be null/undefined on the initial render. Since you probably also want to disable the button if the counter component isn't available for some reason, you can just check if the ref's current value is falsey or if it is truthy and state.seconds of the child greater than 0.
<button
...
disabled={!this.child.current || this.child.current.state.seconds > 0}
onClick={this.resendConfirmationCode}
>
Resend code
</button>
<Counter ref={this.child} />
If we invert the second condition we can combine them into a single comparison using Optional Chaining.
<button
...
disabled={!this.child.current?.state.seconds <= 0}
onClick={this.resendConfirmationCode}
>
Resend code
</button>
<Counter ref={this.child} />
As I'm new to ReactJS, I'm looking to get the data stored in the local storage and display it, basically its a timer, when we enter or set value in the input field, data is stored in the local storage so that on page reload it should'nt loose the data, now i want that data to be displayed in the fieldset present at the end of the code with id="lsOutput", which will get rendered only on button click of id="btnInsert" , any idea how to solve this?
This is the code
class Timer extends Component {
data;
constructor(props) {
super(props);
this.inputHandler = this.inputHandler.bind(this);
this.timerSubmit = this.timerSubmit.bind(this);
this.getData = this.getData.bind(this);
this.state = {
hours: 0,
minutes: 0,
seconds:0
}
this.hoursInput = React.createRef();
this.minutesInput= React.createRef();
this.secondsInput = React.createRef();
}
inputHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
}
timerSubmit = (e) => {
e.preventDefault()
localStorage.setItem('key',JSON.stringify(this.state));
}
getData = (e) => {
e.preventDefault()
console.log( localStorage.getItem('key',JSON.parse(this.state)));
}
componentDidMount() {
this.data = JSON.parse(localStorage.getItem('key'));
if (localStorage.getItem('key')) {
this.setState({
hours: this.data.hours,
minutes: this.data.minutes,
seconds: this.data.seconds
})
} else {
this.setState({
hours: '',
minutes: '',
seconds: ''
})
}
}
convertToSeconds = ( hours, minutes,seconds) => {
return seconds + minutes * 60 + hours * 60 * 60;
}
startTimer = () => {
this.timer = setInterval(this.countDown, 1000);
}
countDown = () => {
const { hours, minutes, seconds } = this.state;
let c_seconds = this.convertToSeconds(hours, minutes, seconds);
if(c_seconds) {
// seconds change
seconds ? this.setState({seconds: seconds-1}) : this.setState({seconds: 59});
// minutes change
if(c_seconds % 60 === 0 && minutes) {
this.setState({minutes: minutes -1});
}
// when only hours entered
if(!minutes && hours) {
this.setState({minutes: 59});
}
// hours change
if(c_seconds % 3600 === 0 && hours) {
this.setState({hours: hours-1});
}
} else {
clearInterval(this.timer);
}
}
stopTimer = () => {
clearInterval(this.timer);
}
resetTimer = () => {
this.setState({
hours: 0,
minutes: 0,
seconds: 0
});
this.hoursInput.current.value = "00";
this.minutesInput.current.value = "00";
this.secondsInput.current.value = "00";
}
render() {
const { hours, minutes, seconds } = this.state;
const inphr = document.getElementById("inphr");
const inpmin = document.getElementById("inpmin");
const btnInsert = document.getElementById("btnInsert");
const lsOutput = document.getElementById("lsOutput");
window.onload = function(){
btnInsert.onclick = () => {
const key = inphr.value;
const value = inpmin.value;
if (key && value) {
localStorage.setItem(key, value);
window.localStorage.reload();
}
};
for (let i = 0; i < localStorage.length; i++ ){
const key = localStorage.key(i);
const value = localStorage.getItem(key);
lsOutput.HTML += `${key}: ${value}<br />`;
}
}
return (
<div className="App">
<div className="inputGroup" onChange={this.timerSubmit}>
<input id="inphr" className="timerinput" ref={this.hoursInput} type="text" placeholder={"00"} name="hours" onChange={this.inputHandler} /><span className="colan">:</span>
<input id="inpmin" className="timerinput" ref={this.minutesInput} type="text" placeholder={"00"} name="minutes" onChange={this.inputHandler} /><span className="colan">:</span>
<input id="inpsec" className="timerinput" ref={this.secondsInput} type="text" placeholder={"00"} name="seconds" onChange={this.inputHandler} />
</div>
<div className="timerbtn" >
<FontAwesomeIcon onClick={this.startTimer} className="start" icon={ faPlayCircle }/>
<FontAwesomeIcon onClick={this.stopTimer} className="stop" icon={ faPauseCircle }/>
<FontAwesomeIcon onClick={this.resetTimer} className="reset" icon={ faUndoAlt }/>
</div>
<h1 className="timercd" > {hours}:{minutes}:{seconds} </h1>
<button id="btnInsert">submit</button>
<label ref={this.hoursInput} id="labelhrs">
</label>
<fieldset>
<legend>
Timer
</legend>
<div id="lsOutput" >
</div>
</fieldset>
</div>
);
}
}
export default Timer;
The title pretty much says it all, but I'll include my somewhat complicated React timer code anyway.
My app is basically an alarm app, and it keeps perfect time most (about 3/4s) of the time. But sometimes when it is in a background tab, the timing is off (15 minutes could take 20 minutes, for example).
So I'm wondering if this is a common problem that anyone knows about. Does my browser send the clock to sleep to save energy perhaps?
I see other browser based alarm clocks on the web, but I haven't been able to find much about this problem.
Thanks,
class Clock extends React.Component {
render() {
return (
<>
<div className="floatLeft">
<div id="timer">
<Countdown updateDB={this.props.updateDB} taskBarOpen={this.props.taskBarOpen} pauseForModal={this.props.pauseForModal} cycle={this.props.cycle} taskBarCounter={this.props.taskBarCounter}>
</Countdown>
</div>
</div>
</>
);
}
}
function Countdown(props) {
const [timer, setTimer] = React.useState({
name: 'timer',
onBreak: false,
firstStart: true,
isPaused: true,
pauseForModal: false,
time: 0,
timeRemaining: 0,
timerHandler: null,
cycle: 0,
timeEqualsTimeRemaning: true,
showToolbar: false
})
const [taskBarCounter] = React.useState({
counter: 0
})
let taskBarProps = props.taskBarCounter
let allowCountdownRestart = (taskBarCounter.counter !== taskBarProps) ? true : false
const context = React.useContext(ApiContext);
let breakDurations = context.prefs
React.useEffect(() => {
if (allowCountdownRestart) {
taskBarCounter.counter = taskBarCounter.counter + 1
allowCountdownRestart = false
} else {
allowCountdownRestart = true
}
}, [props, allowCountdownRestart])
React.useEffect(() => {
if (props.pauseForModal) {
timer.pauseForModal = true
// handlePause()
} else {
setTimeout(() => {
timer.pauseForModal = false
// handleStart()
}, 300);
}
}, [props.pauseForModal])
React.useEffect(() => {
if (allowCountdownRestart) {
if (!timer.pauseForModal) {
setTimer((timer) => ({
...timer,
time: props.cycle * 60,
timeRemaining: props.cycle * 60,
cycle: props.cycle,
onBreak: false
}));
}
if (timer.isPaused) {
// timer.isPaused = false;
}
}
}, [props])
React.useEffect(() => {
if (timer.time === 0 && !timer.firstStart) {
setTimeout(function () {
if (timer.onBreak) {
playBark()
timer.showToolbar = false
timer.onBreak = false
} else {
const breakDuration = breakDurations[timer.cycle] * 60
playTweet()
if (breakDuration !== 0) {
// playTweet()
setTimer((timer) => ({
...timer,
onBreak: true,
time: breakDuration,
timeRemaining: breakDuration
}));
} else {
// playBark()
timer.showToolbar = false
}
props.updateDB(timer.cycle)
}
}, 1000);
} else {
if (timer.time === timer.timeRemaining) {
timer.firstStart = false
timer.showToolbar = true
handleStart()
}
}
}, [timer.time, timer.time === timer.timeRemaining])
React.useEffect(() => {
if (timer.timeRemaining === 0) {
clearInterval(timer.timerHandler)
setTimer((timer) => ({
...timer,
time: 0,
isPaused: true
}));
}
}, [timer.timeRemaining])
const updateTimeRemaining = e => {
setTimer(prev => {
return { ...prev, timeRemaining: prev.timeRemaining - 1 }
})
}
const handleStart = e => {
if (timer.time !== 0) {
clearInterval(timer.timerHandler)
const handle = setInterval(updateTimeRemaining, 1000);
setTimer({ ...timer, isPaused: false, timerHandler: handle })
}
}
const handlePause = e => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, isPaused: true })
}
const timeFormat = (duration) => {
if (duration > 0) {
var hrs = ~~(duration / 3600);
var mins = ~~((duration % 3600) / 60);
var secs = ~~duration % 60;
var ret = "";
if (hrs > 0) {
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
}
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
ret += "" + secs;
return ret;
} else {
return "00:00"
}
}
const handleSkip = () => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, timeRemaining: 0 })
}
const handleStop = () => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, onBreak: true, cycle: 0, timeRemaining: 0 })
}
const [playBark] = useSound(bark,
{ volume: 0.35 }
);
const [playTweet] = useSound(tweet,
{ volume: 0.20 }
);
const [playGong] = useSound(gong,
{ volume: 0.20 }
);
return <React.Fragment>
{timer.onBreak ?
<div><h2 className="display-timer-header">On Break </h2> <h2 className="display-timer">{timeFormat(timer.timeRemaining)}</h2></div>
: <div><h3 className="display-timer-header"> Time Left </h3> <h3 ref={context.timerRef} className="display-timer">{timeFormat(timer.timeRemaining)}</h3></div>}
<div className="toolbar-container">
<div className={`toolbar-icons ${props.taskBarOpen ? "taskbar-open" : ""}`}>
<i className="tooltip"><Stop className="toolbar-icon" onClick={handleStop}></Stop>
<span className="tooltiptext">Stop</span></i>
{!timer.isPaused ?
<i className="tooltip pause"><PausePresentation className="toolbar-icon" onClick={handlePause}></PausePresentation>
<span className="tooltiptext pause-tooltip">Pause</span></i>
:
<i className="tooltip pause"><PlayCircleOutline className="toolbar-icon" onClick={handleStart}></PlayCircleOutline>
<span className="tooltiptext">Start</span></i>
}
<i className="tooltip"><SkipNext className="toolbar-icon" onClick={handleSkip} ></SkipNext>
<span className="tooltiptext">Skip to Break</span></i>
</div>
</div>
</React.Fragment>
}
export default Clock;
I am beginner.
I have a question, I have made a counter timer in React, but unfortunately, it doesn't work properly.
I can't find a mistake, Could someone help me?
import React, { useState, useEffect } from "react";
export default function CountDown() {
let [seconds, setSeconds] = useState(3);
let [minutes, setMinutes] = useState(59);
let [hours, setHours] = useState(3);
useEffect(() => {
const interval = setInterval(() => {
setCounddownTimer();
console.log("i am working", { seconds, minutes, hours });
return () => clearInterval(interval);
}, 1000);
}, []);
const setCounddownTimer = () => {
if (hours === 0 && minutes === 0 && seconds === 0) {
timerReset();
} else if (minutes === 0 && seconds === 0) {
console.log({ seconds, minutes, hours });
setHours(--hours);
setMinutes(59);
setSeconds(59);
} else if (seconds === 0) {
setSeconds(59);
setMinutes(--minutes);
} else {
setSeconds(--seconds);
}
};
const timerReset = () => {
setSeconds(59);
setMinutes(59);
setHours(3);
};
const addLeadingZero = (number) => {
return number < 10 ? "0" + number : number;
};
const style = {
"text-align": "center",
"font-weight": "bold",
color: "#cf0000"
};
return (
<div style={style}>
{addLeadingZero(hours)}:{addLeadingZero(minutes)}:
{addLeadingZero(seconds)}
</div>
);
}
I am also paste a link to codesandbox https://codesandbox.io/s/epic-hopper-f9dy4?file=/src/App.js:0-1277
Your useEffect's return statement was inside the setInterval(). It should be outside.
const interval = setInterval(() => {
setCounddownTimer();
console.log("i am working", { seconds, minutes, hours });
}, 1000);
return () => clearInterval(interval);
UPDATE
Also, you should've put hours, minutes, seconds in useEffect's dependency array, since rendering depends on these 3 states. Otherwise the seconds will count down till the timer reaches 03:58:59, but then the minutes will start counting down.
I have a component that will start counting down from 5 mins when a timestamp (obsTakenTime) is received via props. When the countdown gets <=0, I render ‘Overdue’. At this point I need to clear interval which I think I've done, the issues is when if, I refresh the page the obstimeleft should remain overdue but the countdown automatically starts from 59 mins because the value of nextObservationTime becomes 59min and this.state.obsTimeleft becomes undefined even thought the value of timestamp obsTakenTime is the same. I've looked at other similar threads on SO but I couldn't get mine to work. Any help is appreciated.
similar post - Clear interval in React class
Countdown component
export default class ObservationCountDown extends React.Component {
constructor(props) {
super(props);
this.state = {
obsTimeleft: undefined
};
this.countDown = this.countDown.bind(this);
this.startCounter = this.startCounter.bind(this);
this.countDownInterval = null;
}
countDown() {
const { obsTakenTime} = this.props; //when last obs was taken by the user in ms
const nextDueTimeForObs = moment(obsTakenTime).add(5, 'minutes');
const nextObservationTime = Number(nextDueTimeForObs.subtract(moment.now()).format('m'));
const timeToDisable = 2; // disable buttons time
this.setState({ obsTimeleft: nextObservationTime + ' min' }, () => {
if (nextObservationTime <= Number(timeToDisable)) {
this.props.disablePatientUpdate();
}
if (nextObservationTime <= 0) {
clearInterval(this.countDownInterval); // doesn't work here
this.setState({ obsTimeleft: 'Overdue' }, () => {
if(this.state.obsTimeleft === 'Overdue'){
clearInterval(this.countDownInterval); // doesn't work here
}
});
}
});
}
componentDidMount() {
this.startCounter();
}
startCounter() {
this.countDownInterval = setInterval(this.countDown, 1000);
}
componentDidUpdate(prevProps){
if(this.props.patient.timestamp !== prevProps.patient.timestamp){
this.startCountdown();
}
}
componentWillUnmount(){
clearInterval(this.countDownInterval);
}
render() {
const { obsTimeleft } = this.state;
return (
<>
{(obsTimeleft && obsTimeleft === 'Overdue') ?
<div className="text-danger">
<strong>{obsTimeleft}</strong>
</div> :
<div>
<strong>{.obsTimeleft}</strong>
</div>}
</>
);
}
}
another version of countDown() that I tried and didn't work
countDown() {
const { obsTakenTime } = this.props; // obs duration - when last obs was taken by the user in min
const nextDueTimeForObs = moment(obsTakenTime).add(2, 'minutes');
const nextObservationTime = Number(nextDueTimeForObs.subtract(moment.now()).format('m'));
console.log('nextObservationTime', nextObservationTime);
this.setState({ obsTimeleft: nextObservationTime + ' min' })
if (nextObservationTime <= 0) {
this.setState({ obsTimeleft: 'Overdue' }, () => {
if(this.state.obsTimeleft === 'Overdue') {
clearInterval(this.countDownInterval);
}
});
this.props.enablePatientUpdate();
this.props.resetPatient(patient);
}
}