Countdown is not updating even with SetInterval - javascript

I am trying to create a countdown forge application that takes an input of type date and starts counting based on the actual date.
As an example, I am giving as input "After 3 days from now", But I only get as a result, 2d:23h:59m:59s , ans this result is not updating every second.
Here is the main function:
const useCountdown = (targetDate) => {
const countDownDate = new Date(targetDate).getTime();
const [countDown, setCountDown] = useState(
countDownDate - new Date().getTime()
);
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDownDate - new Date().getTime());
}, 1000);
return () => clearInterval(interval);
}, [countDownDate]);
return getReturnValues(countDown);
};
This is where I display the countdown:
const Edit = () => {
const THREE_DAYS_IN_MS = 3 * 24 * 60 * 60 * 1000;
const NOW_IN_MS = new Date().getTime();
const dateTimeAfterThreeDays = NOW_IN_MS + THREE_DAYS_IN_MS;
return(
<Fragment>
<Text>Time left 123:</Text>
<CountdownTimer targetDate={dateTimeAfterThreeDays} />
</Fragment>
)
};
export const renderFieldView = render(<View />);
I am using Atlassian forge and deploying the app in Jira.

Are you sure you do not have an error in your console?
The only thing I can think of is using milliseconds (Number) as a Date or vice versa.
const { Fragment, useEffect, useState } = React;
const shiftDate = (date) =>
new Date(date.getTime() - date.getTimezoneOffset() * 6e4);
const startOfYearLocal = (date) =>
shiftDate(new Date(date.getFullYear(), 0, 1));
const dayOfYear = (date) =>
Math.floor((date - startOfYearLocal(date)) / 864e5);
const getReturnValues = (dateInMillis) => {
const date = new Date(dateInMillis);
return {
days: dayOfYear(date),
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds(),
};
};
const useCountdown = (targetDate) => {
const countDownDate = new Date(targetDate).getTime();
const [countDown, setCountDown] = useState(countDownDate - Date.now());
useEffect(() => {
const interval = setInterval(() =>
setCountDown(countDownDate - Date.now()), 1000);
return () => clearInterval(interval);
}, [countDownDate]);
return getReturnValues(countDown);
};
const Text = ({ children }) => <span>{children}</span>;
const CountdownTimer = ({ targetDate }) => {
const { days, hours, minutes, seconds } = useCountdown(new Date(targetDate));
return (
<div className="CountdownTimer">
<span>Days:</span><span>{days}</span>
<span>Hours:</span><span>{hours}</span>
<span>Minutes:</span><span>{minutes}</span>
<span>Seconds:</span><span>{seconds}</span>
</div>
);
};
const Edit = () => {
const THREE_DAYS_IN_MS = 2592e5;
const NOW_IN_MS = Date.now();
const dateTimeAfterThreeDays = NOW_IN_MS + THREE_DAYS_IN_MS;
return (
<Fragment>
<Text>Time Left</Text>
<CountdownTimer targetDate={dateTimeAfterThreeDays} />
</Fragment>
);
};
ReactDOM
.createRoot(document.getElementById('root'))
.render(<Edit />);
html, body, #root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#root {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.CountdownTimer {
display: grid;
grid-template-columns: repeat(4, auto);
grid-column-gap: 0.5rem;
grid-row-gap: 0.25rem;
padding: 1rem;
}
.CountdownTimer span {
text-align: right;
}
.CountdownTimer span:nth-child(odd) {
font-weight: bold;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

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 Stopwatch Timer counting up - not resetting the seconds [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;

How can resize boxes, if I remove 2 of them?

How can resize boxes, if I remove 2 of them?
For example, I have 6 boxes, and I remove 2 of them, the other 4 should change their width.
How can I do this, any suggestions?
you can use flex:
.box{
background-color:gray;
height:150px;
margin:25px;
flex:1 1 0;
}
.container{
display:flex;
flex-wrap:wrap;
}
<div class="container">
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
<div class="box">6</div>
</div>
You can use grid, flexbox or even set width manually. Everything is possible in React world. Well, for the last one you have to be quite tricky!
Example of each approach
const { useState, useEffect, useRef } = React;
const getItems = () => Promise.resolve(Array(6).fill(0).map((pr, index) => ({id: index})))
const random = () => Boolean(Math.floor(Math.random() * 1.4))
const App = () => {
const [items, setItems] = useState([]);
const thirdContainerRef = useRef(null);
const [thirdContainerSize, setThirdContainerSize] = useState({
width: 0,
height: 0
});
useEffect(() => {
let isUnmounted = false;
let handle = null;
getItems()
.then(items => {
if(!isUnmounted) {
setItems(items);
}
})
const loop = () => {
getItems()
.then(items => {
if(!isUnmounted) {
let filtered = items.slice(0, 4);
if(random()) {
filtered = items.slice(0, 6);
}
setItems(filtered);
}
})
handle = setTimeout(loop, 1000);
}
handle = setTimeout(loop, 1000);
return () => {
clearTimeout(handle);
isUnmounted = true;
}
}, [])
const computeFlex = () => {
const flexBasis = items.length === 6 ? 25 : 33;
return {
flex: `1 1 ${flexBasis}%`
}
}
const computeGrid = () => {
const numberOfcolumns = items.length === 6 ? 3 : 2;
return {
gridTemplateColumns: `repeat(${numberOfcolumns}, auto)`
}
}
useEffect(() => {
if(thirdContainerRef && thirdContainerRef.current) {
const element = thirdContainerRef.current;
setThirdContainerSize(element.getBoundingClientRect());
}
}, [thirdContainerRef])
const computeSize = () => {
if(!thirdContainerSize.width) {
return {}
}
const containerWidth = thirdContainerSize.width;
const containerHeight = thirdContainerSize.height;
const margin = 5 * 2;
const width = items.length === 6 ? (containerWidth / 3) - margin : (containerWidth / 2) - margin;
const height = containerHeight / 2 - margin;
return {
width: `${width}px`,
height: `${height}px`,
float: `left`
}
}
return <div>
Flexbox
<div className="container">
{items.map(pr => <div key={pr.id} style={computeFlex()} className="item"></div>)}
</div>
Grid
<div style={computeGrid()} className="second-container">
{items.map(pr => <div key={pr.id} className="item"></div>)}
</div>
Width
<div ref={thirdContainerRef} className="third-container">
{items.map(pr => <div style={computeSize()} key={pr.id} className="item"></div>)}
</div>
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
html, body {
height: 100%;
box-sizing: border-box;
margin: 0;
}
.container {
width: 100%;
height: 100px;
display: flex;
flex-wrap: wrap;
}
.second-container {
width: 100%;
height: 100px;
display: grid;
}
.third-container {
width: 100%;
height: 100px;
}
.item {
background: gray;
margin: 5px;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>

backgroundColor doesn't re-render - React

i'm building a sorting algorithm visualizer. And every one of these items is a div with the backgroundColor set to white.
When the algorithm is running he sets the backgroundColor to orange to display which items have changed.
But the problems happens when i reset the array using setState(), because react re-renders the new array perfectly fine, but he never renders the backgroundColor back to white.
This is my component:
import React from 'react'
import './SortingVisualizer.css'
import InsertionSort from '../SortingAlgorithms/InsertionSort'
const NORMAL_COLOR = 'white';
const CHANGED_COLOR = 'red';
const AFTER_CHANGE_COLOR = 'orange';
export default class SortingVisualizer extends React.Component{
constructor(props){
super(props);
this.state = {
arrayToSort: []
};
}
componentDidMount(){
this.resetArray();
}
resetArray(){
const arrayToSort = [];
for (let i = 0; i < 200; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort });
}
insertionSort(){
let sortedArrayAnim = InsertionSort(this.state.arrayToSort);
let arrayToSort = this.state.arrayToSort;
let arrayBars = document.getElementsByClassName('array-item');
let arrayBarsWithColorChanged = [];
//loop through all the animations
for (let index = 0; index < sortedArrayAnim.length; index++) {
const [i,j] = sortedArrayAnim[index];
//setTimeout(() => {
//set changed colors back to normal
if(index !== 0){
arrayBarsWithColorChanged.forEach((element, index) => {
arrayBars[element].style.backgroundColor = AFTER_CHANGE_COLOR;
});
arrayBarsWithColorChanged = [];
}
let temp = arrayToSort[i];
//change array
arrayToSort[i] = arrayToSort[j];
arrayToSort[j] = temp;
//change div bar colors, unl
if(index != sortedArrayAnim.length - 1){
arrayBars[i].style.backgroundColor = CHANGED_COLOR;
arrayBars[j].style.backgroundColor = CHANGED_COLOR;
arrayBarsWithColorChanged.push(i);
arrayBarsWithColorChanged.push(j);
}
this.setState({ arrayToSort })
//}, 10);
}
}
render() {
const {arrayToSort} = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{height: value, backgroundColor: 'white'}}>
</div>
))}
<button onClick={() => this.resetArray()}>Generate new array</button>
<button onClick={() => this.insertionSort()}>Insertion Sort</button>
</ div>
);
}
RandomIntBetweenRange(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
In React you don't manipulate DOM directly and you don't mutate the state (arrayToSort[i] = arrayToSort[j] for example). You change the model (state, props), and the view changes accordingly.
So in your case, you need to include in the state, the array of column values (arrayToSort), the array of pairs to swap (sortedArrayAnim), and a Set of previous changed values (prevChanged). Whenever something changes, the view will update according to those state values.
Demo (see comments in code):
const NORMAL_COLOR = 'white';
const CHANGED_COLOR = 'red';
const AFTER_CHANGE_COLOR = 'orange';
/** this function return the color **/
const getColor = (idx, prevChanged, [current]) => {
if(current && current.includes(idx)) return CHANGED_COLOR; // if it's in current changed pair [i, j]
if(prevChanged.has(idx)) return AFTER_CHANGE_COLOR; // if it was changed before
return NORMAL_COLOR;
}
class SortingVisualizer extends React.Component {
state = {
arrayToSort: [],
sortedArrayAnim: [],
prevChanged: new Set()
};
timeout = null;
componentDidMount() {
this.resetArray();
}
resetArray = () => {
clearTimeout(this.timeout);
const arrayToSort = [];
for (let i = 0; i < 50; i++) {
arrayToSort.push(this.RandomIntBetweenRange(1, 100));
}
this.setState({
arrayToSort,
sortedArrayAnim: [],
prevChanged: new Set()
});
}
animate = (sortedArrayAnim) => {
this.setState(
({
prevChanged,
arrayToSort
}) => {
if(!sortedArrayAnim.length) return { sortedArrayAnim };
const [current] = sortedArrayAnim;
const newArrayToSort = [...arrayToSort]; // clone newArrayToSort
/** flip the values according to current change [i, j] **/
newArrayToSort[current[0]] = arrayToSort[current[1]];
newArrayToSort[current[1]] = arrayToSort[current[0]];
return ({
arrayToSort: newArrayToSort,
sortedArrayAnim,
prevChanged: new Set([...prevChanged, ...current]) // add changed items to the Set
});
},
() => { // when state change is done
const { sortedArrayAnim } = this.state;
// if there are more items to change wait and call animate again
if(sortedArrayAnim.length) {
this.timeout = setTimeout(() => this.animate(sortedArrayAnim.slice(1)), 1000);
}
}
);
}
insertionSort = () => {
const sortedArrayAnim = [[1, 5], [10, 15], [20, 13], [17, 48], [20, 13], [45, 17]]; // InsertionSort(this.state.arrayToSort); // I've used a dummy array
this.animate(sortedArrayAnim);
}
render() {
const { arrayToSort, sortedArrayAnim, prevChanged } = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{
height: `${value}vh`,
backgroundColor: getColor(idx, prevChanged, sortedArrayAnim)
}}>
</div>
))}
<button onClick={this.resetArray}>Generate new array</button>
<button onClick={this.insertionSort}>Insertion Sort</button>
</ div>
);
}
RandomIntBetweenRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
ReactDOM.render(
<SortingVisualizer />,
root
);
.main-div {
display: flex;
align-items: flex-end;
justify-content: space-between;
background: black;
height: 100vh;
padding: 1vmax 1vmax 0 ;
box-sizing: border-box;
}
.array-item {
width: 1vw;
transition: height 0.3s;
}
button {
align-self: flex-start;
}
body {
margin: 0;
}
<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're going to want to keep everything in state in order for react to re-render based on any changes to state.
state = {
backgroundColor: 'white'
}
...
// element
<div className="array-item" key={idx} style={{height: value, backgroundColor: this.state.backgroundColor}}>
replace arrayBars[i].style.backgroundColor = 'orange'; with this.setState({ backgroundColor: 'orange' });
and update:
resetArray(){
const arrayToSort = [];
for (let i = 0; i < 200; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort, backgroundColor: 'white' }); // reset back to white
}

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