React Stopwatch Timer counting up - not resetting the seconds [duplicate] - javascript

I have seen lots of countdown timers in JavaScript and wanted to get one working in React.
I have borrowed this function I found online:
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;
};
And then I have written this code myself
initiateTimer = () => {
let timeLeftVar = this.secondsToTime(60);
this.setState({ timeLeft: timeLeftVar })
};
startTimer = () => {
let interval = setInterval(this.timer, 1000);
this.setState({ interval: interval });
};
timer = () => {
if (this.state.timeLeft >0){
this.setState({ timeLeft: this.state.timeLeft -1 });
}
else {
clearInterval(this.state.interval);
//this.postToSlack();
}
};
Currently onclick it will set the time on screen to: Time Remaining: 1 m : 0 s
But it does not reduce it to Time Remaining: 0 m : 59 s and then Time Remaining: 0 m : 58 s etc etc
I think I need to call the function again with a different parameter. how can I go about doing this ?
Edit: I forgot to say, I would like the functionality so that I can use seconds to minutes & seconds

You have to setState every second with the seconds remaining (every time the interval is called). Here's an example:
class Example extends React.Component {
constructor() {
super();
this.state = { time: {}, seconds: 5 };
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);
}
}
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);
}
}
render() {
return(
<div>
<button onClick={this.startTimer}>Start</button>
m: {this.state.time.m} s: {this.state.time.s}
</div>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('View'));
<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="View"></div>

Here is a solution using hooks, Timer component, I'm replicating same logic above with hooks
import React from 'react'
import { useState, useEffect } from 'react';
const Timer = (props:any) => {
const {initialMinute = 0,initialSeconds = 0} = props;
const [ minutes, setMinutes ] = useState(initialMinute);
const [seconds, setSeconds ] = useState(initialSeconds);
useEffect(()=>{
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ minutes === 0 && seconds === 0
? null
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
export default Timer;

Here is a simple implementation using hooks and useInterval implementation of #dan-abramov
import React, {useState, useEffect, useRef} from 'react'
import './styles.css'
const STATUS = {
STARTED: 'Started',
STOPPED: 'Stopped',
}
const INITIAL_COUNT = 120
export default function CountdownApp() {
const [secondsRemaining, setSecondsRemaining] = useState(INITIAL_COUNT)
const [status, setStatus] = useState(STATUS.STOPPED)
const secondsToDisplay = secondsRemaining % 60
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60
const minutesToDisplay = minutesRemaining % 60
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60
const handleStart = () => {
setStatus(STATUS.STARTED)
}
const handleStop = () => {
setStatus(STATUS.STOPPED)
}
const handleReset = () => {
setStatus(STATUS.STOPPED)
setSecondsRemaining(INITIAL_COUNT)
}
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1)
} else {
setStatus(STATUS.STOPPED)
}
},
status === STATUS.STARTED ? 1000 : null,
// passing null stops the interval
)
return (
<div className="App">
<h1>React Countdown Demo</h1>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div style={{padding: 20}}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
</div>
<div>Status: {status}</div>
</div>
)
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef()
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback
}, [callback])
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay !== null) {
let id = setInterval(tick, delay)
return () => clearInterval(id)
}
}, [delay])
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, '0')
Here is the codesandbox implementation: https://codesandbox.io/s/react-countdown-demo-gtr4u?file=/src/App.js

Basic idea showing counting down using Date.now() instead of subtracting one which will drift over time.
class Example extends React.Component {
constructor() {
super();
this.state = {
time: {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
},
duration: 2 * 60 * 1000,
timer: null
};
this.startTimer = this.start.bind(this);
}
msToTime(duration) {
let milliseconds = parseInt((duration % 1000));
let seconds = Math.floor((duration / 1000) % 60);
let minutes = Math.floor((duration / (1000 * 60)) % 60);
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = hours.toString().padStart(2, '0');
minutes = minutes.toString().padStart(2, '0');
seconds = seconds.toString().padStart(2, '0');
milliseconds = milliseconds.toString().padStart(3, '0');
return {
hours,
minutes,
seconds,
milliseconds
};
}
componentDidMount() {}
start() {
if (!this.state.timer) {
this.state.startTime = Date.now();
this.timer = window.setInterval(() => this.run(), 10);
}
}
run() {
const diff = Date.now() - this.state.startTime;
// If you want to count up
// this.setState(() => ({
// time: this.msToTime(diff)
// }));
// count down
let remaining = this.state.duration - diff;
if (remaining < 0) {
remaining = 0;
}
this.setState(() => ({
time: this.msToTime(remaining)
}));
if (remaining === 0) {
window.clearTimeout(this.timer);
this.timer = null;
}
}
render() {
return ( <
div >
<
button onClick = {
this.startTimer
} > Start < /button> {
this.state.time.hours
}: {
this.state.time.minutes
}: {
this.state.time.seconds
}. {
this.state.time.milliseconds
}:
<
/div>
);
}
}
ReactDOM.render( < Example / > , document.getElementById('View'));
<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="View"></div>

simple resolution:
import React, { useState, useEffect } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const minutes = Math.floor(delay / 60);
const seconds = Math.floor(delay % 60);
useEffect(() => {
const timer = setInterval(() => {
setDelay(delay - 1);
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;

The problem is in your "this" value.
Timer function cannot access the "state" prop because run in a different context. I suggest you to do something like this:
...
startTimer = () => {
let interval = setInterval(this.timer.bind(this), 1000);
this.setState({ interval });
};
As you can see I've added a "bind" method to your timer function. This allows the timer, when called, to access the same "this" of your react component (This is the primary problem/improvement when working with javascript in general).
Another option is to use another arrow function:
startTimer = () => {
let interval = setInterval(() => this.timer(), 1000);
this.setState({ interval });
};

Countdown of user input
Interface Screenshot
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
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});
}
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 = 0;
this.minutesInput.current.value = 0;
this.secondsInput.current.value = 0;
}
render() {
const { hours, minutes, seconds } = this.state;
return (
<div className="App">
<h1 className="title"> (( React Countdown )) </h1>
<div className="inputGroup">
<h3>Hrs</h3>
<input ref={this.hoursInput} type="number" placeholder={0} name="hours" onChange={this.inputHandler} />
<h3>Min</h3>
<input ref={this.minutesInput} type="number" placeholder={0} name="minutes" onChange={this.inputHandler} />
<h3>Sec</h3>
<input ref={this.secondsInput} type="number" placeholder={0} name="seconds" onChange={this.inputHandler} />
</div>
<div>
<button onClick={this.startTimer} className="start">start</button>
<button onClick={this.stopTimer} className="stop">stop</button>
<button onClick={this.resetTimer} className="reset">reset</button>
</div>
<h1> Timer {hours}: {minutes} : {seconds} </h1>
</div>
);
}
}
export default App;

I had the same problem and I found this npm package for a countdown.
install the package
npm install react-countdown --save
or
yarn add react-countdown
import the package to your file
import Countdown from 'react-countdown';
call the imported "Countdown" inside a render method and pass a date
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
or
<Countdown date={new Date("Sat Sep 26 2021")}>
Here is an example for you.
import React from "react";
import ReactDOM from "react-dom";
import Countdown from "react-countdown";
// Random component
const Completionist = () => <span>You are good to go!</span>;
ReactDOM.render(
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
<Completionist />
</Countdown>,
document.getElementById("root")
);
you can see the detailed document here https://www.npmjs.com/package/react-countdown

functionality :
1)Start
2)Reset
functional component
import {useState, useCallback} from 'react';
const defaultCount = 10;
const intervalGap = 300;
const Counter = () => {
const [timerCount, setTimerCount] = useState(defaultCount);
const startTimerWrapper = useCallback((func)=>{
let timeInterval: NodeJS.Timer;
return () => {
if(timeInterval) {
clearInterval(timeInterval)
}
setTimerCount(defaultCount)
timeInterval = setInterval(() => {
func(timeInterval)
}, intervalGap)
}
}, [])
const timer = useCallback(startTimerWrapper((intervalfn: NodeJS.Timeout) => {
setTimerCount((val) => {
if(val === 0 ) {
clearInterval(intervalfn);
return val
}
return val - 1
})
}), [])
return <>
<div> Counter App</div>
<div> <button onClick={timer}>Start/Reset</button></div>
<div> {timerCount}</div>
</>
}
export default Counter;

When you are using functional components the above code is a good option to do it:
import React, { useState, useEffect } from "react";
import { MessageStrip } from "#ui5/webcomponents-react";
import "./Timer.scss";
const nMinuteSeconds = 60;
const nSecondInMiliseconds = 1000;
const convertMinutesToMiliseconds = (minute) =>
minute * nMinuteSeconds * nSecondInMiliseconds;
const convertMilisecondsToHour = (miliseconds) => new Date(miliseconds).toISOString().slice(11, -5);
export default function Counter({ minutes, onTimeOut }) {
let [timerCount, setTimerCount] = useState(
convertMinutesToMiliseconds(minutes)
);
let interval;
useEffect(() => {
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => {
if (timerCount === 0 && interval) {
onTimeOut();
clearInterval(interval);
}
setTimerCount((timerCount -= nSecondInMiliseconds));
}, nSecondInMiliseconds);
}, []);
return (
<>
<MessageStrip design="Information" hide-close-button>
Time left: {convertMilisecondsToHour(timerCount)}
</MessageStrip>
</>
);
}

Here's a simple implementation using a custom hook:
import * as React from 'react';
// future time from where countdown will start decreasing
const futureTime = new Date( Date.now() + 5000 ).getTime(); // adding 5 seconds
export const useCountDown = (stop = false) => {
const [time, setTime] = React.useState(
futureTime - Date.now()
);
// ref to store interval which we can clear out later
// when stop changes through parent component (component that uses this hook)
// causing the useEffect callback to trigger again
const intervalRef = React.useRef<NodeJS.Timer | null>(null);
React.useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
return;
}
const interval = intervalRef.current = setInterval(() => {
setTime(futureTime - Date.now());
}, 1000);
return () => clearInterval(interval);
}, [stop]);
return getReturnValues(time);
};
const getReturnValues = (countDown: number) => {
const days = Math.floor(countDown / (1000 * 60 * 60 * 24));
const hours = Math.floor(
(countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
);
const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((countDown % (1000 * 60)) / 1000);
return [days, hours, minutes, seconds];
};
Example of using this hook:
function App() {
const [timerStopped, stopTimer] = React.useState(false);
const [,hours,minutes,seconds] = useCountDown(timerStopped);
// to stop the interval
if( !timerStopped && minutes + seconds <= 0 ) {
stopTimer(true);
}
return (
<div className="App">
Time Left: {hours}:{minutes}:{seconds}
{ timerStopped ? (
<h1>Time's up</h1>
) : null }
</div>
);
}

A simple 24-hour countdown that can easily be customized to fit different scenarios
setInterval(function time() {
let d = new Date();
let hours = 24 - d.getHours();
let min = 60 - d.getMinutes();
if ((min + "").length == 1) {
min = "0" + min;
}
let sec = 60 - d.getSeconds();
if ((sec + "").length == 1) {
sec = "0" + sec;
}
setState(hours + ":" + min + ":" + sec);
}, 1000);

The one downside with setInterval is that it can slow down the main thread. You can do a countdown timer using requestAnimationFrame instead to prevent this. For example, this is my generic countdown timer component:
class Timer extends Component {
constructor(props) {
super(props)
// here, getTimeRemaining is a helper function that returns an
// object with { total, seconds, minutes, hours, days }
this.state = { timeLeft: getTimeRemaining(props.expiresAt) }
}
// Wait until the component has mounted to start the animation frame
componentDidMount() {
this.start()
}
// Clean up by cancelling any animation frame previously scheduled
componentWillUnmount() {
this.stop()
}
start = () => {
this.frameId = requestAnimationFrame(this.tick)
}
tick = () => {
const timeLeft = getTimeRemaining(this.props.expiresAt)
if (timeLeft.total <= 0) {
this.stop()
// ...any other actions to do on expiration
} else {
this.setState(
{ timeLeft },
() => this.frameId = requestAnimationFrame(this.tick)
)
}
}
stop = () => {
cancelAnimationFrame(this.frameId)
}
render() {...}
}

Here is a TypeScript version of CountDown Timer in React. I used code of brother Masood and M.Georgiev
import React, {useState, useEffect, useCallback} from "react";
const Minute_to_Seconds = 60;
const Seconds_to_milliseconds = 1000;
export interface CounterProps {
minutes:number,
statusAlert: (status: string)=>void,
}
export interface TimerProps {
initialMinute: number,
initialSeconds: number,
}
const Counter: React.FC<CounterProps> = (props) => {
const convert_Minutes_To_MiliSeconds = (minute:number) => {
return minute * Minute_to_Seconds * Seconds_to_milliseconds;
}
const convert_Mili_Seconds_To_Hour = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const convert_Mili_Seconds_To_Minute = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const [timer_State, setTimer_State]=useState(0);
const [timerCount, setTimerCount] = useState(convert_Minutes_To_MiliSeconds(props.minutes));
useEffect(() => {
if (timerCount > 0) {
const interval = setInterval(() => {
if (timer_State === 0) {
props.statusAlert("start");
setTimer_State(1);
}
let tempTimerCount = timerCount;
tempTimerCount -= Seconds_to_milliseconds;
setTimerCount(tempTimerCount);
},
(timer_State === 0)
? 0
: Seconds_to_milliseconds
);
return () => {
clearInterval(interval);
}
}
else{
props.statusAlert("end");
}
}, [
timer_State,
timerCount,
props,
]);
return (
<p>
Time left: {convert_Mili_Seconds_To_Hour(timerCount)}
</p>
);
}
const Timer: React.FC<TimerProps> = (props) => {
const [ minutes, setMinutes ] = useState(props.initialMinute);
const [seconds, setSeconds ] = useState(props.initialSeconds);
useEffect(()=>{
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ ((minutes === 0) && (seconds === 0))
? "Press F5 to Refresh"
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
const RCTAPP=()=> {
const status_Alert2=(status: string)=> {
console.log("__________________________==================== status: ", status);
if (status==="start"){
alert("Timer started");
}
else{
alert("Time's up");
}
}
return (
<div style={{textAlign: "center"}}>
<Counter
minutes={1}
// minutes={0.1}
statusAlert={status_Alert2}
/>
<Timer
initialMinute={0}
initialSeconds={30}
/>
</div>
);
}
export default RCTAPP;

Typescript/Hooks/Shorter version of #Masood's answer
import { useState, useEffect } from 'react';
type Props = {
initMin: number,
initSecs: number
};
const Timer = ({initMins, initSecs}: Props) => {
// Combining useState values together for brevity
const [ [mins, secs], setCountdown ] = useState([initMins, initSecs]);
/**
* Triggers each second and whenever mins/seconds updates itself.
*/
useEffect(() => {
// Timer that decrements itself each second and updates the mins/seconds downwards
let timerInterval = setInterval(() => {
// Countdown timer up, clear timer and do nothing
if (mins === 0 && secs === 0) {
clearInterval(timerInterval);
} else if (secs === 0) {
// Might be correct to set seconds to 59, but not sure
// should decrement from 60 seconds yeah?
setCountdown([mins - 1, 60]);
} else {
setCountdown([mins, secs - 1]);
}
}, 1000);
return () => {
clearInterval(timerInterval);
};
}, [mins, secs]);
return (
<div>
{ mins === 0 && secs === 0
? null
: <h1> {mins}:{secs < 10 ? `0${secs}` : secs}</h1>
}
</div>
)
}
export default Timer;

As we don't want the timer at the highest priority than other states so we will use useTransition hook. delay is the time in seconds 180s = 3min.
import React, { useState, useEffect, useTransition } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const [minutes, setMinutes] = useState(0);
const [seconds, setSeconds] = useState(0);
const [isPending, startTransition] = useTransition();
useEffect(() => {
const timer = setInterval(() => {
startTransition(() => {
setDelay(delay - 1);
setMinutes(Math.floor(delay / 60));
setSeconds(Math.floor(delay % 60));
});
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;

Related

Is there a way of calling an html element before it is defined? [duplicate]

I have seen lots of countdown timers in JavaScript and wanted to get one working in React.
I have borrowed this function I found online:
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;
};
And then I have written this code myself
initiateTimer = () => {
let timeLeftVar = this.secondsToTime(60);
this.setState({ timeLeft: timeLeftVar })
};
startTimer = () => {
let interval = setInterval(this.timer, 1000);
this.setState({ interval: interval });
};
timer = () => {
if (this.state.timeLeft >0){
this.setState({ timeLeft: this.state.timeLeft -1 });
}
else {
clearInterval(this.state.interval);
//this.postToSlack();
}
};
Currently onclick it will set the time on screen to: Time Remaining: 1 m : 0 s
But it does not reduce it to Time Remaining: 0 m : 59 s and then Time Remaining: 0 m : 58 s etc etc
I think I need to call the function again with a different parameter. how can I go about doing this ?
Edit: I forgot to say, I would like the functionality so that I can use seconds to minutes & seconds
You have to setState every second with the seconds remaining (every time the interval is called). Here's an example:
class Example extends React.Component {
constructor() {
super();
this.state = { time: {}, seconds: 5 };
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);
}
}
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);
}
}
render() {
return(
<div>
<button onClick={this.startTimer}>Start</button>
m: {this.state.time.m} s: {this.state.time.s}
</div>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('View'));
<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="View"></div>
Here is a solution using hooks, Timer component, I'm replicating same logic above with hooks
import React from 'react'
import { useState, useEffect } from 'react';
const Timer = (props:any) => {
const {initialMinute = 0,initialSeconds = 0} = props;
const [ minutes, setMinutes ] = useState(initialMinute);
const [seconds, setSeconds ] = useState(initialSeconds);
useEffect(()=>{
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ minutes === 0 && seconds === 0
? null
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
export default Timer;
Here is a simple implementation using hooks and useInterval implementation of #dan-abramov
import React, {useState, useEffect, useRef} from 'react'
import './styles.css'
const STATUS = {
STARTED: 'Started',
STOPPED: 'Stopped',
}
const INITIAL_COUNT = 120
export default function CountdownApp() {
const [secondsRemaining, setSecondsRemaining] = useState(INITIAL_COUNT)
const [status, setStatus] = useState(STATUS.STOPPED)
const secondsToDisplay = secondsRemaining % 60
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60
const minutesToDisplay = minutesRemaining % 60
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60
const handleStart = () => {
setStatus(STATUS.STARTED)
}
const handleStop = () => {
setStatus(STATUS.STOPPED)
}
const handleReset = () => {
setStatus(STATUS.STOPPED)
setSecondsRemaining(INITIAL_COUNT)
}
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1)
} else {
setStatus(STATUS.STOPPED)
}
},
status === STATUS.STARTED ? 1000 : null,
// passing null stops the interval
)
return (
<div className="App">
<h1>React Countdown Demo</h1>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div style={{padding: 20}}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
</div>
<div>Status: {status}</div>
</div>
)
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef()
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback
}, [callback])
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay !== null) {
let id = setInterval(tick, delay)
return () => clearInterval(id)
}
}, [delay])
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, '0')
Here is the codesandbox implementation: https://codesandbox.io/s/react-countdown-demo-gtr4u?file=/src/App.js
Basic idea showing counting down using Date.now() instead of subtracting one which will drift over time.
class Example extends React.Component {
constructor() {
super();
this.state = {
time: {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
},
duration: 2 * 60 * 1000,
timer: null
};
this.startTimer = this.start.bind(this);
}
msToTime(duration) {
let milliseconds = parseInt((duration % 1000));
let seconds = Math.floor((duration / 1000) % 60);
let minutes = Math.floor((duration / (1000 * 60)) % 60);
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = hours.toString().padStart(2, '0');
minutes = minutes.toString().padStart(2, '0');
seconds = seconds.toString().padStart(2, '0');
milliseconds = milliseconds.toString().padStart(3, '0');
return {
hours,
minutes,
seconds,
milliseconds
};
}
componentDidMount() {}
start() {
if (!this.state.timer) {
this.state.startTime = Date.now();
this.timer = window.setInterval(() => this.run(), 10);
}
}
run() {
const diff = Date.now() - this.state.startTime;
// If you want to count up
// this.setState(() => ({
// time: this.msToTime(diff)
// }));
// count down
let remaining = this.state.duration - diff;
if (remaining < 0) {
remaining = 0;
}
this.setState(() => ({
time: this.msToTime(remaining)
}));
if (remaining === 0) {
window.clearTimeout(this.timer);
this.timer = null;
}
}
render() {
return ( <
div >
<
button onClick = {
this.startTimer
} > Start < /button> {
this.state.time.hours
}: {
this.state.time.minutes
}: {
this.state.time.seconds
}. {
this.state.time.milliseconds
}:
<
/div>
);
}
}
ReactDOM.render( < Example / > , document.getElementById('View'));
<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="View"></div>
simple resolution:
import React, { useState, useEffect } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const minutes = Math.floor(delay / 60);
const seconds = Math.floor(delay % 60);
useEffect(() => {
const timer = setInterval(() => {
setDelay(delay - 1);
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;
The problem is in your "this" value.
Timer function cannot access the "state" prop because run in a different context. I suggest you to do something like this:
...
startTimer = () => {
let interval = setInterval(this.timer.bind(this), 1000);
this.setState({ interval });
};
As you can see I've added a "bind" method to your timer function. This allows the timer, when called, to access the same "this" of your react component (This is the primary problem/improvement when working with javascript in general).
Another option is to use another arrow function:
startTimer = () => {
let interval = setInterval(() => this.timer(), 1000);
this.setState({ interval });
};
Countdown of user input
Interface Screenshot
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
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});
}
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 = 0;
this.minutesInput.current.value = 0;
this.secondsInput.current.value = 0;
}
render() {
const { hours, minutes, seconds } = this.state;
return (
<div className="App">
<h1 className="title"> (( React Countdown )) </h1>
<div className="inputGroup">
<h3>Hrs</h3>
<input ref={this.hoursInput} type="number" placeholder={0} name="hours" onChange={this.inputHandler} />
<h3>Min</h3>
<input ref={this.minutesInput} type="number" placeholder={0} name="minutes" onChange={this.inputHandler} />
<h3>Sec</h3>
<input ref={this.secondsInput} type="number" placeholder={0} name="seconds" onChange={this.inputHandler} />
</div>
<div>
<button onClick={this.startTimer} className="start">start</button>
<button onClick={this.stopTimer} className="stop">stop</button>
<button onClick={this.resetTimer} className="reset">reset</button>
</div>
<h1> Timer {hours}: {minutes} : {seconds} </h1>
</div>
);
}
}
export default App;
I had the same problem and I found this npm package for a countdown.
install the package
npm install react-countdown --save
or
yarn add react-countdown
import the package to your file
import Countdown from 'react-countdown';
call the imported "Countdown" inside a render method and pass a date
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
or
<Countdown date={new Date("Sat Sep 26 2021")}>
Here is an example for you.
import React from "react";
import ReactDOM from "react-dom";
import Countdown from "react-countdown";
// Random component
const Completionist = () => <span>You are good to go!</span>;
ReactDOM.render(
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
<Completionist />
</Countdown>,
document.getElementById("root")
);
you can see the detailed document here https://www.npmjs.com/package/react-countdown
functionality :
1)Start
2)Reset
functional component
import {useState, useCallback} from 'react';
const defaultCount = 10;
const intervalGap = 300;
const Counter = () => {
const [timerCount, setTimerCount] = useState(defaultCount);
const startTimerWrapper = useCallback((func)=>{
let timeInterval: NodeJS.Timer;
return () => {
if(timeInterval) {
clearInterval(timeInterval)
}
setTimerCount(defaultCount)
timeInterval = setInterval(() => {
func(timeInterval)
}, intervalGap)
}
}, [])
const timer = useCallback(startTimerWrapper((intervalfn: NodeJS.Timeout) => {
setTimerCount((val) => {
if(val === 0 ) {
clearInterval(intervalfn);
return val
}
return val - 1
})
}), [])
return <>
<div> Counter App</div>
<div> <button onClick={timer}>Start/Reset</button></div>
<div> {timerCount}</div>
</>
}
export default Counter;
When you are using functional components the above code is a good option to do it:
import React, { useState, useEffect } from "react";
import { MessageStrip } from "#ui5/webcomponents-react";
import "./Timer.scss";
const nMinuteSeconds = 60;
const nSecondInMiliseconds = 1000;
const convertMinutesToMiliseconds = (minute) =>
minute * nMinuteSeconds * nSecondInMiliseconds;
const convertMilisecondsToHour = (miliseconds) => new Date(miliseconds).toISOString().slice(11, -5);
export default function Counter({ minutes, onTimeOut }) {
let [timerCount, setTimerCount] = useState(
convertMinutesToMiliseconds(minutes)
);
let interval;
useEffect(() => {
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => {
if (timerCount === 0 && interval) {
onTimeOut();
clearInterval(interval);
}
setTimerCount((timerCount -= nSecondInMiliseconds));
}, nSecondInMiliseconds);
}, []);
return (
<>
<MessageStrip design="Information" hide-close-button>
Time left: {convertMilisecondsToHour(timerCount)}
</MessageStrip>
</>
);
}
Here's a simple implementation using a custom hook:
import * as React from 'react';
// future time from where countdown will start decreasing
const futureTime = new Date( Date.now() + 5000 ).getTime(); // adding 5 seconds
export const useCountDown = (stop = false) => {
const [time, setTime] = React.useState(
futureTime - Date.now()
);
// ref to store interval which we can clear out later
// when stop changes through parent component (component that uses this hook)
// causing the useEffect callback to trigger again
const intervalRef = React.useRef<NodeJS.Timer | null>(null);
React.useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
return;
}
const interval = intervalRef.current = setInterval(() => {
setTime(futureTime - Date.now());
}, 1000);
return () => clearInterval(interval);
}, [stop]);
return getReturnValues(time);
};
const getReturnValues = (countDown: number) => {
const days = Math.floor(countDown / (1000 * 60 * 60 * 24));
const hours = Math.floor(
(countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
);
const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((countDown % (1000 * 60)) / 1000);
return [days, hours, minutes, seconds];
};
Example of using this hook:
function App() {
const [timerStopped, stopTimer] = React.useState(false);
const [,hours,minutes,seconds] = useCountDown(timerStopped);
// to stop the interval
if( !timerStopped && minutes + seconds <= 0 ) {
stopTimer(true);
}
return (
<div className="App">
Time Left: {hours}:{minutes}:{seconds}
{ timerStopped ? (
<h1>Time's up</h1>
) : null }
</div>
);
}
A simple 24-hour countdown that can easily be customized to fit different scenarios
setInterval(function time() {
let d = new Date();
let hours = 24 - d.getHours();
let min = 60 - d.getMinutes();
if ((min + "").length == 1) {
min = "0" + min;
}
let sec = 60 - d.getSeconds();
if ((sec + "").length == 1) {
sec = "0" + sec;
}
setState(hours + ":" + min + ":" + sec);
}, 1000);
The one downside with setInterval is that it can slow down the main thread. You can do a countdown timer using requestAnimationFrame instead to prevent this. For example, this is my generic countdown timer component:
class Timer extends Component {
constructor(props) {
super(props)
// here, getTimeRemaining is a helper function that returns an
// object with { total, seconds, minutes, hours, days }
this.state = { timeLeft: getTimeRemaining(props.expiresAt) }
}
// Wait until the component has mounted to start the animation frame
componentDidMount() {
this.start()
}
// Clean up by cancelling any animation frame previously scheduled
componentWillUnmount() {
this.stop()
}
start = () => {
this.frameId = requestAnimationFrame(this.tick)
}
tick = () => {
const timeLeft = getTimeRemaining(this.props.expiresAt)
if (timeLeft.total <= 0) {
this.stop()
// ...any other actions to do on expiration
} else {
this.setState(
{ timeLeft },
() => this.frameId = requestAnimationFrame(this.tick)
)
}
}
stop = () => {
cancelAnimationFrame(this.frameId)
}
render() {...}
}
Here is a TypeScript version of CountDown Timer in React. I used code of brother Masood and M.Georgiev
import React, {useState, useEffect, useCallback} from "react";
const Minute_to_Seconds = 60;
const Seconds_to_milliseconds = 1000;
export interface CounterProps {
minutes:number,
statusAlert: (status: string)=>void,
}
export interface TimerProps {
initialMinute: number,
initialSeconds: number,
}
const Counter: React.FC<CounterProps> = (props) => {
const convert_Minutes_To_MiliSeconds = (minute:number) => {
return minute * Minute_to_Seconds * Seconds_to_milliseconds;
}
const convert_Mili_Seconds_To_Hour = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const convert_Mili_Seconds_To_Minute = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const [timer_State, setTimer_State]=useState(0);
const [timerCount, setTimerCount] = useState(convert_Minutes_To_MiliSeconds(props.minutes));
useEffect(() => {
if (timerCount > 0) {
const interval = setInterval(() => {
if (timer_State === 0) {
props.statusAlert("start");
setTimer_State(1);
}
let tempTimerCount = timerCount;
tempTimerCount -= Seconds_to_milliseconds;
setTimerCount(tempTimerCount);
},
(timer_State === 0)
? 0
: Seconds_to_milliseconds
);
return () => {
clearInterval(interval);
}
}
else{
props.statusAlert("end");
}
}, [
timer_State,
timerCount,
props,
]);
return (
<p>
Time left: {convert_Mili_Seconds_To_Hour(timerCount)}
</p>
);
}
const Timer: React.FC<TimerProps> = (props) => {
const [ minutes, setMinutes ] = useState(props.initialMinute);
const [seconds, setSeconds ] = useState(props.initialSeconds);
useEffect(()=>{
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ ((minutes === 0) && (seconds === 0))
? "Press F5 to Refresh"
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
const RCTAPP=()=> {
const status_Alert2=(status: string)=> {
console.log("__________________________==================== status: ", status);
if (status==="start"){
alert("Timer started");
}
else{
alert("Time's up");
}
}
return (
<div style={{textAlign: "center"}}>
<Counter
minutes={1}
// minutes={0.1}
statusAlert={status_Alert2}
/>
<Timer
initialMinute={0}
initialSeconds={30}
/>
</div>
);
}
export default RCTAPP;
Typescript/Hooks/Shorter version of #Masood's answer
import { useState, useEffect } from 'react';
type Props = {
initMin: number,
initSecs: number
};
const Timer = ({initMins, initSecs}: Props) => {
// Combining useState values together for brevity
const [ [mins, secs], setCountdown ] = useState([initMins, initSecs]);
/**
* Triggers each second and whenever mins/seconds updates itself.
*/
useEffect(() => {
// Timer that decrements itself each second and updates the mins/seconds downwards
let timerInterval = setInterval(() => {
// Countdown timer up, clear timer and do nothing
if (mins === 0 && secs === 0) {
clearInterval(timerInterval);
} else if (secs === 0) {
// Might be correct to set seconds to 59, but not sure
// should decrement from 60 seconds yeah?
setCountdown([mins - 1, 60]);
} else {
setCountdown([mins, secs - 1]);
}
}, 1000);
return () => {
clearInterval(timerInterval);
};
}, [mins, secs]);
return (
<div>
{ mins === 0 && secs === 0
? null
: <h1> {mins}:{secs < 10 ? `0${secs}` : secs}</h1>
}
</div>
)
}
export default Timer;
As we don't want the timer at the highest priority than other states so we will use useTransition hook. delay is the time in seconds 180s = 3min.
import React, { useState, useEffect, useTransition } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const [minutes, setMinutes] = useState(0);
const [seconds, setSeconds] = useState(0);
const [isPending, startTransition] = useTransition();
useEffect(() => {
const timer = setInterval(() => {
startTransition(() => {
setDelay(delay - 1);
setMinutes(Math.floor(delay / 60));
setSeconds(Math.floor(delay % 60));
});
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;

(React)Child component pause its useEffect when the parent re-renders

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

How to get Pomodoro Clock to work on CodePen?

The following code for a React Pomodoro Clock has been input into CodePen exactly into the CodePen JS editor and HTML editor, and then save clicked. However, CodePen does not display the Pomodoro Clock, and where the clock should appear there is only a blank orange page. There is no indication why on CodePen. Actual link: https://codepen.io/controlunit/pen/dyMrMxy Any help would be greatly appreciated.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
/*
* A simple React component
*/
const initState = {
breakLength: 5,
sessionLength: 25,
init: 'session',
stateIndex: 0,
timeLeft: undefined,
timeLeftSeconds: undefined,
started: false,
intervalFunc: undefined
}
const states = [ { name: 'session', duration: 1500 }, { name: 'break', duration: 300 } ]
const secondsToMins = (time) => {
let converted = ('0' + Math.floor(time / 60)).slice(-2) + ':' + ('0' + Math.floor(time % 60)).slice(-2);
return converted;
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = initState;
this.breakDecrement = this.breakDecrement.bind(this);
this.breakIncrement = this.breakIncrement.bind(this);
this.sessionDecrement = this.sessionDecrement.bind(this);
this.sessionIncrement = this.sessionIncrement.bind(this);
this.startStop = this.startStop.bind(this);
this.reset = this.reset.bind(this);
}
componentDidMount() {
let sessionSeconds = this.state.sessionLength * 60;
this.setState({ timeLeftSeconds: sessionSeconds });
this.setState({ timeLeft: secondsToMins(sessionSeconds) });
}
breakDecrement() {
// decrements the breakLength and the breakSeconds
// breakLength is only a number ie. 5 (does not show seconds)
// breakSeconds is that nunber converted into seconds
let breakLength = this.state.breakLength - 1;
if (breakLength > 0 && breakLength < 61){
this.setState({ breakLength: breakLength });
let breakSeconds = breakLength * 60;
states[1]['duration'] = breakSeconds;
}
}
breakIncrement() {
// same as decrement except does increment
let breakLength = this.state.breakLength + 1;
if (breakLength > 0 && breakLength < 61){
this.setState({ breakLength: breakLength });
let breakSeconds = breakLength * 60;
states[1]['duration'] = breakSeconds;
}
}
sessionDecrement() {
// decrements the sessionLength and the sessionSeconds
// sessionLength is only a number ie. 25 (does not show seconds)
// sessionSeconds is that nunber converted into seconds
let sessionLength = this.state.sessionLength - 1;
if (sessionLength > 0 && sessionLength < 61){
states[0]['duration'] = sessionLength*60;
this.setState(prevState => ({
sessionLength: prevState.sessionLength-1,
timeLeftSeconds: (prevState.sessionLength-1)*60,
timeLeft: secondsToMins((prevState.sessionLength-1)*60)})
);
}
}
sessionIncrement() {
// same as decrement except does increment
let sessionLength = this.state.sessionLength + 1;
if (sessionLength > 0 && sessionLength < 61){
states[0]['duration'] = sessionLength*60;
this.setState(prevState => ({
sessionLength: prevState.sessionLength+1,
timeLeftSeconds: (prevState.sessionLength+1)*60,
timeLeft: secondsToMins((prevState.sessionLength+1)*60)})
);
}
}
startStop(id) {
// starts the countDown, which runs continuously until the start/stop button
// is pressed again, which pauses the countdown.
// the id parameter is used by countDown to play the audio beep
if(!this.state.started){
this.countDown(id);
this.setState({ started: true});
}
// pauses the countDown
if(this.state.started){
let intervalFunc = this.state.intervalFunc;
clearInterval(intervalFunc);
this.setState({ started: false});
}
}
reset() {
let intervalFunc = this.state.intervalFunc;
clearInterval(intervalFunc);
// reset state to default values
this.setState({ breakLength: 5 });
this.setState({ sessionLength: 25 });
this.setState({ init: 'session' });
this.setState({ timeLeftSeconds: 1500})
this.setState({ timeLeft: '25:00' });
this.setState({ stateIndex: 0 });
this.setState({ started: false });
this.setState({ intervalFunc: undefined });
}
countDown(id){
// set the function to a variable and set state to it, so the function
// can be paused with clearInterval()
var intervalFunc = setInterval(() => down(this.state.timeLeftSeconds--), 1000);
this.setState({intervalFunc: intervalFunc});
const down = (time) =>
{
if(time > 0){
// converts seconds to MM:SS at every t-minus
this.setState({ timeLeft: secondsToMins(time)});
console.log(time);
console.log(this.state.timeLeft);
}
if (time <= 0) {
let sound = document.getElementById(id).childNodes[0];
sound.play();
let stateIndex = (this.state.stateIndex + 1) % states.length;
this.setState({ stateIndex: stateIndex});
this.setState({ timeLeftSeconds: states[stateIndex].duration});
this.setState({ init: states[stateIndex].name});
this.setState({ timeLeft: secondsToMins(time)});
console.log(time);
console.log(this.state.timeLeft);
console.log(this.state.init);
}
}
}
render() {
return (
<div id="clock">
<h1 id="title">25-5 Clock</h1>
<div>
<p id="break-label">Break Length</p>
<p id="break-length">{this.state.breakLength}</p>
<button id="break-decrement" onClick={e => this.breakDecrement()}> Decrease </button>
<button id="break-increment" onClick={e => this.breakIncrement()}> Increase </button>
</div>
<div>
<p id="session-label">Session Length</p>
<p id="session-length">{this.state.sessionLength}</p>
<button id="session-decrement" onClick={e => this.sessionDecrement()}> Decrease </button>
<button id="session-increment" onClick={e => this.sessionIncrement()}> Increase </button>
</div>
<hr/>
<div>
<p id="timer-label">{this.state.init}</p>
<p id="time-left">{this.state.timeLeft}</p>
<button id="start_stop" onClick={e => this.startStop(e.target.id)}><audio id="beep" src='./beep.mp3'></audio> start/stop </button>
<button id="reset" onClick={e => this.reset()}> reset </button>
</div>
</div>
);
}
};
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Clock />, document.getElementById("app"));
index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>25-5 Clock</title>
<style>
</style>
</head>
<body>
<main>
<div id="app"></app>
</main>
</body>
</html>
It's because you're using JSX in your javascript without transpiling it. Go into codepen's settings and under the javascript tab select 'babel' as the javascript preprocessor.

A click to start the countdown timer and a second one to Pause the timer in the same button

I want to click on (Start/Pause) Button in order start the coundown in the first click and the second click to pause it.It should be two options in the same button to start and pause the timer.
Here a descriptive image :
A created a separated button for start and pause the countdown but i want to make them in the same button.
Here my React Js code:
import React from "react";
export default class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
secondsElapsed: 1800000 / 1000 //time in seconds
};
}
getHours() {
return ("0" + Math.floor(this.state.secondsElapsed / 3600)).slice(-2);
}
getMinutes() {
return ("0" + Math.floor((this.state.secondsElapsed % 3600) / 60)).slice(
-2
);
}
getSeconds() {
return ("0" + (this.state.secondsElapsed % 60)).slice(-2);
}
startTime() {
var _this = this;
this.countdown = setInterval(function() {
_this.setState({ secondsElapsed: _this.state.secondsElapsed - 1 });
}, 1000);
}
resetTime() {
this.reset = this.setState({
secondsElapsed: (this.state.secondsElapsed = 0)
});
}
pauseTime() {
clearInterval(this.countdown);
}
render() {
return (
<div className="App">
<div className="timer-container">
<div className="bloc-timer"> {this.getHours()}</div>
<div className="bloc-timer"> :{this.getMinutes()}</div>
<div className="bloc-timer"> :{this.getSeconds()}</div>
</div>
<div>
<button onClick={() => this.startTime()}>Start</button>
<button onClick={() => this.pauseTime()}>Pause</button>
<button onClick={() => this.resetTime()}>Reset</button>
</div>
</div>
);
}
}
You can add an additional state variable isActive that keeps track of if your timer is active or not and choose what function to call when clicking the button based on that.
Example
class Timer extends React.Component {
state = {
isActive: false,
secondsElapsed: 1800000 / 1000 //time in seconds
};
getHours() {
return ("0" + Math.floor(this.state.secondsElapsed / 3600)).slice(-2);
}
getMinutes() {
return ("0" + Math.floor((this.state.secondsElapsed % 3600) / 60)).slice(
-2
);
}
getSeconds() {
return ("0" + (this.state.secondsElapsed % 60)).slice(-2);
}
startTime = () => {
this.setState({ isActive: true });
this.countdown = setInterval(() => {
this.setState(({ secondsElapsed }) => ({
secondsElapsed: secondsElapsed - 1
}));
}, 1000);
};
resetTime = () => {
clearInterval(this.countdown);
this.setState({
secondsElapsed: 1800000 / 1000,
isActive: false
});
};
pauseTime = () => {
clearInterval(this.countdown);
this.setState({ isActive: false });
};
render() {
return (
<div className="App">
<div className="timer-container">
<span className="bloc-timer"> {this.getHours()}</span>
<span className="bloc-timer"> :{this.getMinutes()}</span>
<span className="bloc-timer"> :{this.getSeconds()}</span>
</div>
<div>
<button
onClick={this.state.isActive ? this.pauseTime : this.startTime}
>
Start/Pause
</button>
<button onClick={this.resetTime}>Reset</button>
</div>
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<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>
<div id="root"></div>
You can add a Boolean to check the state of your timer. And trigger timer start or stop depending on its value. Basically add another function to change the state, and call startTime if it hasn't been started, and pauseTime it if has been started.

Countdown timer in React

I have seen lots of countdown timers in JavaScript and wanted to get one working in React.
I have borrowed this function I found online:
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;
};
And then I have written this code myself
initiateTimer = () => {
let timeLeftVar = this.secondsToTime(60);
this.setState({ timeLeft: timeLeftVar })
};
startTimer = () => {
let interval = setInterval(this.timer, 1000);
this.setState({ interval: interval });
};
timer = () => {
if (this.state.timeLeft >0){
this.setState({ timeLeft: this.state.timeLeft -1 });
}
else {
clearInterval(this.state.interval);
//this.postToSlack();
}
};
Currently onclick it will set the time on screen to: Time Remaining: 1 m : 0 s
But it does not reduce it to Time Remaining: 0 m : 59 s and then Time Remaining: 0 m : 58 s etc etc
I think I need to call the function again with a different parameter. how can I go about doing this ?
Edit: I forgot to say, I would like the functionality so that I can use seconds to minutes & seconds
You have to setState every second with the seconds remaining (every time the interval is called). Here's an example:
class Example extends React.Component {
constructor() {
super();
this.state = { time: {}, seconds: 5 };
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);
}
}
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);
}
}
render() {
return(
<div>
<button onClick={this.startTimer}>Start</button>
m: {this.state.time.m} s: {this.state.time.s}
</div>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('View'));
<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="View"></div>
Here is a solution using hooks, Timer component, I'm replicating same logic above with hooks
import React from 'react'
import { useState, useEffect } from 'react';
const Timer = (props:any) => {
const {initialMinute = 0,initialSeconds = 0} = props;
const [ minutes, setMinutes ] = useState(initialMinute);
const [seconds, setSeconds ] = useState(initialSeconds);
useEffect(()=>{
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ minutes === 0 && seconds === 0
? null
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
export default Timer;
Here is a simple implementation using hooks and useInterval implementation of #dan-abramov
import React, {useState, useEffect, useRef} from 'react'
import './styles.css'
const STATUS = {
STARTED: 'Started',
STOPPED: 'Stopped',
}
const INITIAL_COUNT = 120
export default function CountdownApp() {
const [secondsRemaining, setSecondsRemaining] = useState(INITIAL_COUNT)
const [status, setStatus] = useState(STATUS.STOPPED)
const secondsToDisplay = secondsRemaining % 60
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60
const minutesToDisplay = minutesRemaining % 60
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60
const handleStart = () => {
setStatus(STATUS.STARTED)
}
const handleStop = () => {
setStatus(STATUS.STOPPED)
}
const handleReset = () => {
setStatus(STATUS.STOPPED)
setSecondsRemaining(INITIAL_COUNT)
}
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1)
} else {
setStatus(STATUS.STOPPED)
}
},
status === STATUS.STARTED ? 1000 : null,
// passing null stops the interval
)
return (
<div className="App">
<h1>React Countdown Demo</h1>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div style={{padding: 20}}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
</div>
<div>Status: {status}</div>
</div>
)
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef()
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback
}, [callback])
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay !== null) {
let id = setInterval(tick, delay)
return () => clearInterval(id)
}
}, [delay])
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, '0')
Here is the codesandbox implementation: https://codesandbox.io/s/react-countdown-demo-gtr4u?file=/src/App.js
Basic idea showing counting down using Date.now() instead of subtracting one which will drift over time.
class Example extends React.Component {
constructor() {
super();
this.state = {
time: {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
},
duration: 2 * 60 * 1000,
timer: null
};
this.startTimer = this.start.bind(this);
}
msToTime(duration) {
let milliseconds = parseInt((duration % 1000));
let seconds = Math.floor((duration / 1000) % 60);
let minutes = Math.floor((duration / (1000 * 60)) % 60);
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = hours.toString().padStart(2, '0');
minutes = minutes.toString().padStart(2, '0');
seconds = seconds.toString().padStart(2, '0');
milliseconds = milliseconds.toString().padStart(3, '0');
return {
hours,
minutes,
seconds,
milliseconds
};
}
componentDidMount() {}
start() {
if (!this.state.timer) {
this.state.startTime = Date.now();
this.timer = window.setInterval(() => this.run(), 10);
}
}
run() {
const diff = Date.now() - this.state.startTime;
// If you want to count up
// this.setState(() => ({
// time: this.msToTime(diff)
// }));
// count down
let remaining = this.state.duration - diff;
if (remaining < 0) {
remaining = 0;
}
this.setState(() => ({
time: this.msToTime(remaining)
}));
if (remaining === 0) {
window.clearTimeout(this.timer);
this.timer = null;
}
}
render() {
return ( <
div >
<
button onClick = {
this.startTimer
} > Start < /button> {
this.state.time.hours
}: {
this.state.time.minutes
}: {
this.state.time.seconds
}. {
this.state.time.milliseconds
}:
<
/div>
);
}
}
ReactDOM.render( < Example / > , document.getElementById('View'));
<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="View"></div>
simple resolution:
import React, { useState, useEffect } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const minutes = Math.floor(delay / 60);
const seconds = Math.floor(delay % 60);
useEffect(() => {
const timer = setInterval(() => {
setDelay(delay - 1);
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;
The problem is in your "this" value.
Timer function cannot access the "state" prop because run in a different context. I suggest you to do something like this:
...
startTimer = () => {
let interval = setInterval(this.timer.bind(this), 1000);
this.setState({ interval });
};
As you can see I've added a "bind" method to your timer function. This allows the timer, when called, to access the same "this" of your react component (This is the primary problem/improvement when working with javascript in general).
Another option is to use another arrow function:
startTimer = () => {
let interval = setInterval(() => this.timer(), 1000);
this.setState({ interval });
};
Countdown of user input
Interface Screenshot
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
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});
}
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 = 0;
this.minutesInput.current.value = 0;
this.secondsInput.current.value = 0;
}
render() {
const { hours, minutes, seconds } = this.state;
return (
<div className="App">
<h1 className="title"> (( React Countdown )) </h1>
<div className="inputGroup">
<h3>Hrs</h3>
<input ref={this.hoursInput} type="number" placeholder={0} name="hours" onChange={this.inputHandler} />
<h3>Min</h3>
<input ref={this.minutesInput} type="number" placeholder={0} name="minutes" onChange={this.inputHandler} />
<h3>Sec</h3>
<input ref={this.secondsInput} type="number" placeholder={0} name="seconds" onChange={this.inputHandler} />
</div>
<div>
<button onClick={this.startTimer} className="start">start</button>
<button onClick={this.stopTimer} className="stop">stop</button>
<button onClick={this.resetTimer} className="reset">reset</button>
</div>
<h1> Timer {hours}: {minutes} : {seconds} </h1>
</div>
);
}
}
export default App;
I had the same problem and I found this npm package for a countdown.
install the package
npm install react-countdown --save
or
yarn add react-countdown
import the package to your file
import Countdown from 'react-countdown';
call the imported "Countdown" inside a render method and pass a date
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
or
<Countdown date={new Date("Sat Sep 26 2021")}>
Here is an example for you.
import React from "react";
import ReactDOM from "react-dom";
import Countdown from "react-countdown";
// Random component
const Completionist = () => <span>You are good to go!</span>;
ReactDOM.render(
<Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>
<Completionist />
</Countdown>,
document.getElementById("root")
);
you can see the detailed document here https://www.npmjs.com/package/react-countdown
functionality :
1)Start
2)Reset
functional component
import {useState, useCallback} from 'react';
const defaultCount = 10;
const intervalGap = 300;
const Counter = () => {
const [timerCount, setTimerCount] = useState(defaultCount);
const startTimerWrapper = useCallback((func)=>{
let timeInterval: NodeJS.Timer;
return () => {
if(timeInterval) {
clearInterval(timeInterval)
}
setTimerCount(defaultCount)
timeInterval = setInterval(() => {
func(timeInterval)
}, intervalGap)
}
}, [])
const timer = useCallback(startTimerWrapper((intervalfn: NodeJS.Timeout) => {
setTimerCount((val) => {
if(val === 0 ) {
clearInterval(intervalfn);
return val
}
return val - 1
})
}), [])
return <>
<div> Counter App</div>
<div> <button onClick={timer}>Start/Reset</button></div>
<div> {timerCount}</div>
</>
}
export default Counter;
When you are using functional components the above code is a good option to do it:
import React, { useState, useEffect } from "react";
import { MessageStrip } from "#ui5/webcomponents-react";
import "./Timer.scss";
const nMinuteSeconds = 60;
const nSecondInMiliseconds = 1000;
const convertMinutesToMiliseconds = (minute) =>
minute * nMinuteSeconds * nSecondInMiliseconds;
const convertMilisecondsToHour = (miliseconds) => new Date(miliseconds).toISOString().slice(11, -5);
export default function Counter({ minutes, onTimeOut }) {
let [timerCount, setTimerCount] = useState(
convertMinutesToMiliseconds(minutes)
);
let interval;
useEffect(() => {
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => {
if (timerCount === 0 && interval) {
onTimeOut();
clearInterval(interval);
}
setTimerCount((timerCount -= nSecondInMiliseconds));
}, nSecondInMiliseconds);
}, []);
return (
<>
<MessageStrip design="Information" hide-close-button>
Time left: {convertMilisecondsToHour(timerCount)}
</MessageStrip>
</>
);
}
Here's a simple implementation using a custom hook:
import * as React from 'react';
// future time from where countdown will start decreasing
const futureTime = new Date( Date.now() + 5000 ).getTime(); // adding 5 seconds
export const useCountDown = (stop = false) => {
const [time, setTime] = React.useState(
futureTime - Date.now()
);
// ref to store interval which we can clear out later
// when stop changes through parent component (component that uses this hook)
// causing the useEffect callback to trigger again
const intervalRef = React.useRef<NodeJS.Timer | null>(null);
React.useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
return;
}
const interval = intervalRef.current = setInterval(() => {
setTime(futureTime - Date.now());
}, 1000);
return () => clearInterval(interval);
}, [stop]);
return getReturnValues(time);
};
const getReturnValues = (countDown: number) => {
const days = Math.floor(countDown / (1000 * 60 * 60 * 24));
const hours = Math.floor(
(countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
);
const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((countDown % (1000 * 60)) / 1000);
return [days, hours, minutes, seconds];
};
Example of using this hook:
function App() {
const [timerStopped, stopTimer] = React.useState(false);
const [,hours,minutes,seconds] = useCountDown(timerStopped);
// to stop the interval
if( !timerStopped && minutes + seconds <= 0 ) {
stopTimer(true);
}
return (
<div className="App">
Time Left: {hours}:{minutes}:{seconds}
{ timerStopped ? (
<h1>Time's up</h1>
) : null }
</div>
);
}
A simple 24-hour countdown that can easily be customized to fit different scenarios
setInterval(function time() {
let d = new Date();
let hours = 24 - d.getHours();
let min = 60 - d.getMinutes();
if ((min + "").length == 1) {
min = "0" + min;
}
let sec = 60 - d.getSeconds();
if ((sec + "").length == 1) {
sec = "0" + sec;
}
setState(hours + ":" + min + ":" + sec);
}, 1000);
The one downside with setInterval is that it can slow down the main thread. You can do a countdown timer using requestAnimationFrame instead to prevent this. For example, this is my generic countdown timer component:
class Timer extends Component {
constructor(props) {
super(props)
// here, getTimeRemaining is a helper function that returns an
// object with { total, seconds, minutes, hours, days }
this.state = { timeLeft: getTimeRemaining(props.expiresAt) }
}
// Wait until the component has mounted to start the animation frame
componentDidMount() {
this.start()
}
// Clean up by cancelling any animation frame previously scheduled
componentWillUnmount() {
this.stop()
}
start = () => {
this.frameId = requestAnimationFrame(this.tick)
}
tick = () => {
const timeLeft = getTimeRemaining(this.props.expiresAt)
if (timeLeft.total <= 0) {
this.stop()
// ...any other actions to do on expiration
} else {
this.setState(
{ timeLeft },
() => this.frameId = requestAnimationFrame(this.tick)
)
}
}
stop = () => {
cancelAnimationFrame(this.frameId)
}
render() {...}
}
Here is a TypeScript version of CountDown Timer in React. I used code of brother Masood and M.Georgiev
import React, {useState, useEffect, useCallback} from "react";
const Minute_to_Seconds = 60;
const Seconds_to_milliseconds = 1000;
export interface CounterProps {
minutes:number,
statusAlert: (status: string)=>void,
}
export interface TimerProps {
initialMinute: number,
initialSeconds: number,
}
const Counter: React.FC<CounterProps> = (props) => {
const convert_Minutes_To_MiliSeconds = (minute:number) => {
return minute * Minute_to_Seconds * Seconds_to_milliseconds;
}
const convert_Mili_Seconds_To_Hour = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const convert_Mili_Seconds_To_Minute = (miliseconds:number) => {
return new Date(miliseconds).toISOString().slice(11, -5);
}
const [timer_State, setTimer_State]=useState(0);
const [timerCount, setTimerCount] = useState(convert_Minutes_To_MiliSeconds(props.minutes));
useEffect(() => {
if (timerCount > 0) {
const interval = setInterval(() => {
if (timer_State === 0) {
props.statusAlert("start");
setTimer_State(1);
}
let tempTimerCount = timerCount;
tempTimerCount -= Seconds_to_milliseconds;
setTimerCount(tempTimerCount);
},
(timer_State === 0)
? 0
: Seconds_to_milliseconds
);
return () => {
clearInterval(interval);
}
}
else{
props.statusAlert("end");
}
}, [
timer_State,
timerCount,
props,
]);
return (
<p>
Time left: {convert_Mili_Seconds_To_Hour(timerCount)}
</p>
);
}
const Timer: React.FC<TimerProps> = (props) => {
const [ minutes, setMinutes ] = useState(props.initialMinute);
const [seconds, setSeconds ] = useState(props.initialSeconds);
useEffect(()=>{
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
});
return (
<div>
{ ((minutes === 0) && (seconds === 0))
? "Press F5 to Refresh"
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
const RCTAPP=()=> {
const status_Alert2=(status: string)=> {
console.log("__________________________==================== status: ", status);
if (status==="start"){
alert("Timer started");
}
else{
alert("Time's up");
}
}
return (
<div style={{textAlign: "center"}}>
<Counter
minutes={1}
// minutes={0.1}
statusAlert={status_Alert2}
/>
<Timer
initialMinute={0}
initialSeconds={30}
/>
</div>
);
}
export default RCTAPP;
Typescript/Hooks/Shorter version of #Masood's answer
import { useState, useEffect } from 'react';
type Props = {
initMin: number,
initSecs: number
};
const Timer = ({initMins, initSecs}: Props) => {
// Combining useState values together for brevity
const [ [mins, secs], setCountdown ] = useState([initMins, initSecs]);
/**
* Triggers each second and whenever mins/seconds updates itself.
*/
useEffect(() => {
// Timer that decrements itself each second and updates the mins/seconds downwards
let timerInterval = setInterval(() => {
// Countdown timer up, clear timer and do nothing
if (mins === 0 && secs === 0) {
clearInterval(timerInterval);
} else if (secs === 0) {
// Might be correct to set seconds to 59, but not sure
// should decrement from 60 seconds yeah?
setCountdown([mins - 1, 60]);
} else {
setCountdown([mins, secs - 1]);
}
}, 1000);
return () => {
clearInterval(timerInterval);
};
}, [mins, secs]);
return (
<div>
{ mins === 0 && secs === 0
? null
: <h1> {mins}:{secs < 10 ? `0${secs}` : secs}</h1>
}
</div>
)
}
export default Timer;
As we don't want the timer at the highest priority than other states so we will use useTransition hook. delay is the time in seconds 180s = 3min.
import React, { useState, useEffect, useTransition } from "react";
const Timer = ({ delayResend = "180" }) => {
const [delay, setDelay] = useState(+delayResend);
const [minutes, setMinutes] = useState(0);
const [seconds, setSeconds] = useState(0);
const [isPending, startTransition] = useTransition();
useEffect(() => {
const timer = setInterval(() => {
startTransition(() => {
setDelay(delay - 1);
setMinutes(Math.floor(delay / 60));
setSeconds(Math.floor(delay % 60));
});
}, 1000);
if (delay === 0) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
});
return (
<>
<span>
{minutes}:{seconds}
</span>
</>
);
};
export default Timer;

Categories

Resources