67

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

Anfuca
  • 1,293
  • 1
  • 14
  • 27
The worm
  • 4,618
  • 11
  • 31
  • 48
  • 1
    One of [the React documentation examples](https://facebook.github.io/react/docs/state-and-lifecycle.html) is a clock that updates itself, seems like it would be fairly useful... – T.J. Crowder Nov 30 '16 at 10:31
  • @T.J.Crowder it is semi helpful. they are just getting a time though as can return it through componentDidMount whereas I only want to extract seconds and minutes from a starting position.. – The worm Nov 30 '16 at 10:50
  • Perhaps you could put a runnable [mcve] in the question using Stack Snippets, which [support React and JSX](http://meta.stackoverflow.com/questions/338537/how-do-i-create-a-reactjs-stack-snippet-with-jsx-support), so we could see the problem in action. – T.J. Crowder Nov 30 '16 at 10:53
  • @T.J.Crowder finding it very difficult to create one in JSfiddle as I am using many components with many props across many files – The worm Nov 30 '16 at 11:04
  • @T.J.Crowder from the question, what makes sense to you? (to see if I can add more knowledge to things explained less well) – The worm Nov 30 '16 at 11:04
  • @T.J.Crowder sorry for the spam. What I need more is a way to convert seconds to seconds/minutes e.g. 10 -> 00:10 or 65 -> 01:05 in react. basically a nice way to format my state – The worm Nov 30 '16 at 11:25

13 Answers13

88

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>
Brynner Ferreira
  • 1,497
  • 1
  • 21
  • 20
Fabian Schultz
  • 16,616
  • 4
  • 47
  • 54
  • this looks good.one problem is that it does not stop at 0 and goes to minus though? fix that and I'll accept it ;) – The worm Nov 30 '16 at 11:43
  • 1
    Well it's similar to what you've had in your initial code. Check if there's any seconds left and then do `clearInterval`. Updated my answer. – Fabian Schultz Nov 30 '16 at 11:51
  • You could also do a lot more optimizations, like resetting the timer, pausing, etc., but the question was targeted at how do count down and reflect that in the render. – Fabian Schultz Nov 30 '16 at 11:57
  • cheers, mine is still going into minus for some weird reason. I even console.logged(seconds) and it showed me it being 0 so will have to debug further – The worm Nov 30 '16 at 12:09
  • in fact it even got into that part but clear interval did not seem to do anything, you know why? – The worm Nov 30 '16 at 13:16
  • Is your timer object in the state? Try it like I did. – Fabian Schultz Nov 30 '16 at 13:17
  • yeh I have I think. and yes it is in the state at the top. – The worm Nov 30 '16 at 13:18
  • Try it as a global variable, not state (`this.timer = 0;`) – Fabian Schultz Nov 30 '16 at 13:20
  • globals get messy though don't they :( – The worm Nov 30 '16 at 13:22
  • found the problem! – The worm Nov 30 '16 at 13:23
  • 2
    @FabianSchultz your solution was awesome. It was really helpful for me to build my count down timer component and to get started. The code was very clean. Keep up the great work ! – Ravindra Ranwala Apr 26 '17 at 06:53
  • beware that if the component is unmounted before the timer is done a warning will be displayed saying that no `this.setState` can be called on an unmounted object. In order to avoid this you should add `componentWillUnmount(){ clearInterval(this.timer) }` to your component – Jonathan Morales Vélez Dec 27 '17 at 22:28
  • @JonathanMoralesVélez Yes, that’s very true! Left it out here for simplicity. – Fabian Schultz Dec 27 '17 at 22:29
  • @FabianSchultz setInterval may have a delay which is likely to vary per phone, I've seen this to sometimes be up to 30 seconds off. https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified instead look at something like https://dbaron.org/log/20100309-faster-timeouts – mc. Jan 26 '18 at 16:33
  • One additional comment regarding countDown method: If you need previous state to calculate next (which is the case), you should use setState which receives a function that has previousState as argument and returns the newState: this.setState(prevState => calculateNextState(prevState)); – Arthur Rizzo May 30 '18 at 17:59
  • Trying to reset this and start timer again with a button click `resetTimer() { let timeLeftVar = this.secondsToTime(TIMER_IN_SECONDS); this.setState({ timer: 0, seconds: TIMER_IN_SECONDS, time: timeLeftVar }); this.startTimer(); }` does not seem to work – Temi 'Topsy' Bello Aug 09 '20 at 09:00
  • This will not be accurate, time will drift – epascarello Oct 06 '20 at 14:59
  • your `secondsToTime` needs to divide the seconds by anther `1000`, so it should be: `hours = Math.floor(secs / (60 * 60 * 1000))` & `divisor_for_minutes = secs % (60 * 60 * 1000)` – Biskrem Muhammad Dec 07 '21 at 20:39
45

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;
Masood
  • 691
  • 6
  • 10
  • 1
    You initialize and clear the interval every one second, I think it's better to put empty array as the dependency of the useEffect – Israel kusayev Sep 22 '21 at 14:57
  • 1
    @Israelkusayev if i add [] array, it will triger only once, i need to add [seconds,minutes], which will again work same so – Masood Feb 10 '22 at 12:16
  • Nice code. But, I believe it should be setTimeout not setInterval – Ike Anya May 15 '22 at 20:09
7

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>
Ankit Verma
  • 676
  • 5
  • 15
7

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

abumalick
  • 1,883
  • 21
  • 25
5

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>
epascarello
  • 195,511
  • 20
  • 184
  • 225
  • This is a much more stable method. Problem however is, that pause and continue is a bit more challenging. – Tosh Mar 15 '21 at 13:07
  • @Tosh not really.... you know how much time elapsed when it is pasued, you store that. On continue you figure out the difference and set a new start time. – epascarello Mar 15 '21 at 13:18
3

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 });
};
Vaibhav Mule
  • 4,771
  • 3
  • 34
  • 52
Roberto Conte Rosito
  • 2,070
  • 12
  • 22
3

Countdown of user input

Interface Screenshot 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;

3

I had the same problem and I found this npm package for a countdown.

  1. install the package

    npm install react-countdown --save or

    yarn add react-countdown

  2. import the package to your file

    import Countdown from 'react-countdown';

  3. 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

Kasun Shanaka
  • 520
  • 1
  • 5
  • 12
1

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;
1

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>
    </>
  );
}
M.Georgiev
  • 33
  • 4
0

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() {...}
}
Sia
  • 8,166
  • 5
  • 28
  • 49
  • 1
    Nice! But I think you could optimize by preventing too many render. You don't have to `setState` (and rerender) every frame (~30 per sec). You could `setState` only if `timeLeft` (in seconds) changes. And maybe use `shouldComponentUpdate` ? Am I right ? – TeChn4K Jul 20 '18 at 13:32
0

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;
0

In react native:

Usage:

timestamp prop must be in seconds

const refTimer = useRef();
  
  const timerCallbackFunc = timerFlag => {
    // Setting timer flag to finished
    console.warn(
      'You can alert the user by letting him know that Timer is out.',
    );
  };
    
    
 <Timer
 ref={refTimer}
 timestamp={moment(item?.time_left).diff(moment(), 'seconds')}
 timerCallback={timerCallbackFunc}
 textStyle={styles.timerTextAHL}
 />

Timer.js

import React, {
  useState,
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
} from 'react';
import { Text, View } from 'react-native';

const Timer = forwardRef((props, ref) => {
  // For Total seconds
  const [timeStamp, setTimeStamp] = useState(
    props.timestamp ? props.timestamp : 0,
  );
  // Delay Required
  const [delay, setDelay] = useState(props.delay ? props.delay : 1000);

  // Flag for informing parent component when timer is over
  const [sendOnce, setSendOnce] = useState(true);

  // Flag for final display time format
  const [finalDisplayTime, setFinalDisplayTime] = useState('');

  useInterval(() => {
    if (timeStamp > 0) {
      setTimeStamp(timeStamp - 1);
    } else if (sendOnce) {
      if (props.timerCallback) {
        props.timerCallback(true);
      } else {
        console.log('Please pass a callback function...');
      }
      setSendOnce(false);
    }
    setFinalDisplayTime(secondsToDhms(timeStamp));
  }, delay);

  function secondsToDhms(seconds) {
    seconds = Number(seconds);
    var d = Math.floor(seconds / (3600 * 24));
    var h = Math.floor((seconds % (3600 * 24)) / 3600);
    var m = Math.floor((seconds % 3600) / 60);
    var s = Math.floor(seconds % 60);

    var dDisplay = d > 0 ? d + 'd ' : '';
    var hDisplay = h > 0 ? h + 'h ' : '';
    var mDisplay = m > 0 ? m + 'm ' : '';
    var sDisplay = s > 0 ? s + 's ' : '';
    return dDisplay + hDisplay + mDisplay + sDisplay;
  }

  const refTimer = useRef();
  useImperativeHandle(ref, () => ({
    resetTimer: () => {
      // Clearing days, hours, minutes and seconds
      // Clearing Timestamp
      setTimeStamp(props.timestamp);
      setSendOnce(true);
    },
  }));

  return (
    <View ref={refTimer} style={props.containerStyle}>
      <Text style={props.textStyle}>{sendOnce ? finalDisplayTime : '0'}</Text>
    </View>
  );
});

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest function.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => {
        clearInterval(id);
      };
    }
  }, [delay]);
}

export default Timer;

enter image description here

Ajmal Hasan
  • 469
  • 6
  • 5