React Timer Implementation

Basic example

export function Timer() {
  const [isRunning, setIsRunning] = useState(false);
  const [elapsedTime, setElapsedTime] = useState(0);
 
  useEffect(() => {
    let intervalId: NodeJS.Timeout;
 
    if (isRunning) {
      intervalId = setInterval(() => {
        setElapsedTime((prev) => {
          const newTime = prev + 1;
          return newTime;
        });
      }, 1000);
    }
 
    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
    };
  }, [isRunning, onDurationChange]);
 
  const toggleTimer = () => {
    setIsRunning(!isRunning);
  };
 
  return (...);
}

Take inactive tab into consideration

export function Timer() {
  const [isRunning, setIsRunning] = useState(false);
  const [elapsedTime, setElapsedTime] = useState(0);
  const [startTime, setStartTime] = useState<number | null>(null);
 
  useEffect(() => {
    let intervalId: NodeJS.Timeout;
 
    const handleVisibilityChange = () => {
      if (document.hidden) {
        if (intervalId) {
          clearInterval(intervalId);
        }
      } else if (isRunning && startTime) {
        const now = Date.now();
        const timeDiff = Math.floor((now - startTime) / 1000);
        setElapsedTime(timeDiff);
      }
    };
 
    if (isRunning) {
      setStartTime(startTime || Date.now() - elapsedTime * 1000);
      intervalId = setInterval(() => {
        const now = Date.now();
        if (startTime) {
          const timeDiff = Math.floor((now - startTime) / 1000);
          setElapsedTime(timeDiff);
        }
      }, 1000);
 
      document.addEventListener("visibilitychange", handleVisibilityChange);
    }
 
    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, [isRunning, startTime, elapsedTime]);
 
  const toggleTimer = () => {
    if (!isRunning) {
      setStartTime(Date.now() - elapsedTime * 1000);
    } else {
      setStartTime(null);
    }
    setIsRunning(!isRunning);
  };
 
  return (...);
}

Reference