DEV Community

Cover image for Countdown component in react native
Ajmal Hasan
Ajmal Hasan

Posted on Edited on

Countdown component in react native

1. Countdown component that can be updated and also used in lists/flatlist.

Code:

import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, AppState } from 'react-native';
import { translate } from '../../../helpers/Localization';

class CountDown extends React.Component {
  static propTypes = {
    id: PropTypes.string,
    until: PropTypes.number,
    onChange: PropTypes.func,
    onFinish: PropTypes.func,
  };

  state = {
    until: Math.max(this.props.until, 0),
    lastUntil: null,
    wentBackgroundAt: null,
  };

  constructor(props) {
    super(props);
    this.timer = setInterval(this.updateTimer, 1000);
  }

  componentDidMount() {
    this.appStateSubscription = AppState.addEventListener(
      'change',
      this._handleAppStateChange,
    );
  }

  componentWillUnmount() {
    clearInterval(this.timer);
    this.appStateSubscription.remove();
  }

  componentDidUpdate(prevProps, prevState) {
    if (
      this.props.until !== prevProps.until ||
      this.props.id !== prevProps.id
    ) {
      this.setState({
        lastUntil: 0,
        until: Math.max(this.props.until, 0),
      });
    }
  }

  _handleAppStateChange = currentAppState => {
    const { until, wentBackgroundAt } = this.state;
    if (
      currentAppState === 'active' &&
      wentBackgroundAt &&
      this.props.running
    ) {
      const diff = (Date.now() - wentBackgroundAt) / 1000.0;
      this.setState({
        lastUntil: until,
        until: Math.max(0, until - diff),
      });
    }
    if (currentAppState === 'background') {
      this.setState({ wentBackgroundAt: Date.now() });
    }
  };

  secondsToDhms = () => {
    const { until } = this.state;
    let seconds = Number(until);
    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  ' : d > 0 ? '12h  ' : '';
    var mDisplay = m > 0 ? m + 'm  ' : h > 0 ? '60m  ' : '';
    var sDisplay = s > 0 ? s + 's ' : m > 0 ? '60s' : '';
    return dDisplay + hDisplay + mDisplay + sDisplay;
  };

  updateTimer = () => {
    if (this.state.lastUntil === this.state.until || !this.props.running) {
      return;
    }
    if (
      this.state.until === 1 ||
      (this.state.until === 0 && this.state.lastUntil !== 1)
    ) {
      if (this.props.onFinish) {
        this.props.onFinish();
      }
      if (this.props.onChange) {
        this.props.onChange(this.state.until);
      }
    }

    if (this.state.until === 0) {
      this.setState({ lastUntil: 0, until: 0 });
    } else {
      if (this.props.onChange) {
        this.props.onChange(this.state.until);
      }
      this.setState({
        lastUntil: this.state.until,
        until: Math.max(0, this.state.until - 1),
      });
    }
  };

  renderCountDown = () => {
    return (
      <Text style={this.props.textStyle}>
        {this.secondsToDhms() || translate('timeOver')}
      </Text>
    );
  };

  render() {
    return <View style={this.props.style}>{this.renderCountDown()}</View>;
  }
}

CountDown.defaultProps = {
  until: 0,
  size: 15,
  running: true,
};

export default CountDown;


Enter fullscreen mode Exit fullscreen mode

USAGE

          <CountdownComponent
            until={moment(item?.time_left).diff(
              moment(),
              'seconds',
            )}
            textStyle={styles.timerTextAHL}
          />
Enter fullscreen mode Exit fullscreen mode

2. Countdown component using hooks:

/**
 * CountDownTimer Component
 */

import moment from 'moment';
import React, {
  useState,
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
} from 'react';
import {Text, View} from 'react-native';
import {strings} from '../../i18n';

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

  // For days, hours, minutes and seconds
  const [, setDays] = useState(props.days ? props.days : 0);
  const [, setHours] = useState(props.hours ? props.hours : 0);
  const [, setMinutes] = useState(props.minutes ? props.minutes : 0);
  const [, setSeconds] = useState(props.seconds ? props.seconds : 0);

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

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

  const formatTime = time => {
    return String(time).padStart(2, '0');
  };

  useInterval(() => {
    if (timeStamp > 0) {
      setTimeStamp(moment(props.endDate).diff(moment(), 'seconds'));
    } else if (sendOnce) {
      if (props.timerCallback) {
        props.timerCallback(true);
      } else {
        console.log('Please pass a callback function...');
      }
      setSendOnce(false);
    }

    let secondsLeft = Number(timeStamp);
    let d = Math.floor(secondsLeft / (3600 * 24));
    let h = Math.floor((secondsLeft % (3600 * 24)) / 3600);
    let m = Math.floor((secondsLeft % 3600) / 60);
    let s = Math.floor(secondsLeft % 60);

    let dDisplay = d > 0 ? d + strings('timeShortForm.timeDays') + ' ' : '';
    let hDisplay = h > 0 ? h + strings('timeShortForm.timHrs') + ' ' : '';
    let mDisplay =
      m > 0
        ? formatTime(m) + strings('timeShortForm.timeMin') + ' '
        : h > 0
        ? '00' + strings('timeShortForm.timeMin') + ' '
        : '';
    let sDisplay =
      s > 0
        ? formatTime(s) + strings('timeShortForm.timeSec') + ' '
        : m > 0
        ? '00' + strings('timeShortForm.timeSec') + ' '
        : '';

    // setDays(d);
    // setHours(h);
    // setMinutes(m);
    // setSeconds(s);
    if (dDisplay?.length) {
      setFinalDisplayTime(dDisplay + hDisplay + mDisplay);
    } else {
      setFinalDisplayTime(hDisplay + mDisplay + sDisplay);
    }
  }, delay);

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

  return !finalDisplayTime ? null : (
    <View ref={refTimer} style={props.containerStyle}>
      <Text style={props.textStyle}>
        {props.preText
          ? props.preText + ' ' + finalDisplayTime
          : finalDisplayTime}
      </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 CountDownTimer;

Enter fullscreen mode Exit fullscreen mode

Usage:

  const refTimer = useRef();

        <CountdownComponent
          ref={refTimer}
          timestamp={moment(endDate).diff(moment(), 'seconds')}
          endDate={endDate}
          textStyle={styles.timingHeader}
          timerCallback={onPress}
        />
Enter fullscreen mode Exit fullscreen mode

Top comments (0)