Countdown timer in React

后端 未结 7 812
甜味超标
甜味超标 2020-11-30 00:05

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:

secondsToTim         


        
相关标签:
7条回答
  • 2020-11-30 00:27

    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 });
    };
    
    0 讨论(0)
  • 2020-11-30 00:29

    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>

    0 讨论(0)
  • 2020-11-30 00:36

    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>

    0 讨论(0)
  • 2020-11-30 00:37

    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>

    0 讨论(0)
  • 2020-11-30 00:37

    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() {...}
    }
    
    0 讨论(0)
  • 2020-11-30 00:44

    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;
    
    0 讨论(0)
提交回复
热议问题