ahbeng/NUSMods

View on GitHub
website/src/views/hooks/useCurrentTime.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { useRef, useState, useEffect } from 'react';
import { differenceInMilliseconds } from 'date-fns';

import { forceTimer } from 'utils/debug';

function getCurrentTime() {
  return forceTimer() || new Date();
}

/**
 * Hook version of withTimer
 */
export default function useCurrentTime(intervalInMs: number = 60 * 1000) {
  const intervalId = useRef<number>();
  const [time, setTime] = useState(getCurrentTime());

  useEffect(() => {
    // Update current time every interval milliseconds
    intervalId.current = window.setInterval(() => {
      setTime(getCurrentTime());
    }, intervalInMs);

    // Page visibility changes when tabs go in and out of focus. When tabs
    // are out of focus, mobile browsers slow down timers, so we run an
    // additional check to make sure the page state has not drifted too far
    // from the wall clock
    const onPageVisibilityChange = () => {
      setTime((prevTime) => {
        const now = getCurrentTime();
        if (!document.hidden && differenceInMilliseconds(now, prevTime) > intervalInMs) {
          return now;
        }
        return prevTime;
      });
    };

    document.addEventListener('visibilitychange', onPageVisibilityChange);

    return () => {
      window.clearTimeout(intervalId.current);
      document.removeEventListener('visibilitychange', onPageVisibilityChange);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Technically this can just be getCurrentTime(), but we use state so we can trigger rerender
  return time;
}