Countdown component in react native
Tags: reactnative, javascript, react
Published: 2022-01-30 15:28:43

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


```

**USAGE**

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

---

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

```

**Usage:**

```
  const refTimer = useRef();

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