Skip to content

useStopwatch

React hook to create a stopwatch functionality.

Add the hook via the CLI:

sh
npx @novajslabs/cli add useStopwatch
sh
npx @novajslabs/cli add useStopwatch
sh
pnpm dlx @novajslabs/cli add useStopwatch

Or copy and paste the code into your project:

ts
import { useState, useEffect } from "react";

const addLeadingZero = (digit: number): string => {
  let timeStr = "";

  digit % 10 === digit ? (timeStr += `0${digit}`) : (timeStr += `${digit}`);

  return timeStr;
};

interface Stopwatch {
  current: string;
  isPaused: boolean;
  isOver: boolean;
  currentDays: number;
  currentHours: number;
  currentMinutes: number;
  currentSeconds: number;
  elapsedSeconds: number;
  pause: () => void;
  play: () => void;
  reset: () => void;
  togglePause: () => void;
}

export const useStopwatch = (): Stopwatch => {
  const [time, setTime] = useState({
    days: 0,
    hours: 0,
    minutes: 0,
    seconds: 0,
  });
  const [paused, setPaused] = useState(false);
  const divider = ":";
  const [isOver, setIsOver] = useState(false);

  useEffect(() => {
    if (paused) {
      return;
    }

    const interval = setInterval(() => {
      setTime((prev) => {
        let d = prev.days;
        let h = prev.hours;
        let m = prev.minutes;
        let s = prev.seconds;

        if (s + 1 >= 60) {
          s = 0;
          if (m + 1 >= 60) {
            m = 0;
            if (h + 1 >= 24) {
              h = 0;
              d++;
            } else {
              h++;
            }
          } else {
            m++;
          }
        } else {
          s++;
        }

        return { days: d, hours: h, minutes: m, seconds: s };
      });
    }, 1000);

    return () => clearInterval(interval);
  }, [time, paused]);

  return {
    current: `${addLeadingZero(time.days)}${divider}${addLeadingZero(
      time.hours
    )}${divider}${addLeadingZero(time.minutes)}${divider}${addLeadingZero(
      time.seconds
    )}`,
    isPaused: paused,
    isOver,
    currentDays: time.days,
    currentHours: time.hours,
    currentMinutes: time.minutes,
    currentSeconds: time.seconds,
    elapsedSeconds:
      time.days * 86400 + time.hours * 3600 + time.minutes * 60 + time.seconds,
    pause: () => setPaused(true),
    play: () => setPaused(false),
    reset: () => {
      setIsOver(false);
      setTime({ days: 0, hours: 0, minutes: 0, seconds: 0 });
    },
    togglePause: () => {
      setPaused(!paused);
    },
  };
};
js
import { useState, useEffect } from "react";

const addLeadingZero = (digit) => {
  let timeStr = "";

  digit % 10 === digit ? (timeStr += `0${digit}`) : (timeStr += `${digit}`);

  return timeStr;
};

export const useStopwatch = () => {
  const [time, setTime] = useState({
    days: 0,
    hours: 0,
    minutes: 0,
    seconds: 0,
  });
  const [paused, setPaused] = useState(false);
  const divider = ":";
  const [isOver, setIsOver] = useState(false);

  useEffect(() => {
    if (paused) {
      return;
    }

    const interval = setInterval(() => {
      setTime((prev) => {
        let d = prev.days;
        let h = prev.hours;
        let m = prev.minutes;
        let s = prev.seconds;

        if (s + 1 >= 60) {
          s = 0;
          if (m + 1 >= 60) {
            m = 0;
            if (h + 1 >= 24) {
              h = 0;
              d++;
            } else {
              h++;
            }
          } else {
            m++;
          }
        } else {
          s++;
        }

        return { days: d, hours: h, minutes: m, seconds: s };
      });
    }, 1000);

    return () => clearInterval(interval);
  }, [time, paused]);

  return {
    current: `${addLeadingZero(time.days)}${divider}${addLeadingZero(
      time.hours
    )}${divider}${addLeadingZero(time.minutes)}${divider}${addLeadingZero(
      time.seconds
    )}`,
    isPaused: paused,
    isOver,
    currentDays: time.days,
    currentHours: time.hours,
    currentMinutes: time.minutes,
    currentSeconds: time.seconds,
    elapsedSeconds:
      time.days * 86400 + time.hours * 3600 + time.minutes * 60 + time.seconds,
    pause: () => setPaused(true),
    play: () => setPaused(false),
    reset: () => {
      setIsOver(false);
      setTime({ days: 0, hours: 0, minutes: 0, seconds: 0 });
    },
    togglePause: () => {
      setPaused(!paused);
    },
  };
};

Requirements

React 16.8 or higher

Return values

current

Type: string

The current value of the stopwatch.

isPaused

Type: boolean

Represents whether the stopwatch is paused (true) or not (false).

isOver

Type: boolean

Represents whether the stopwatch has ended (true) or not (false).

currentDays

Type: number

The current value of days on the stopwatch.

currentHours

Type: number

The current value of hours on the stopwatch.

currentMinutes

Type: number

The current value of minutes on the stopwatch.

currentSeconds

Type: number

The current value of the seconds in the stopwatch.

elapsedSeconds

Type: number

The number of seconds that have passed since the start of the stopwatch.

pause

Type: function

Pause the stopwatch.

play

Type: function

Play the stopwatch.

reset

Type: function

Reset the stopwatch.

togglePause

Type: function

Toggle between pausing and playing the stopwatch.

Example

tsx
import { useStopwatch } from "./hooks/useStopwatch";

const App = () => {
  const {
    current,
    isPaused,
    isOver,
    currentDays,
    currentHours,
    currentMinutes,
    currentSeconds,
    elapsedSeconds,
    pause,
    play,
    reset,
    togglePause,
  } = useStopwatch();

  return (
    <div>
      <p>Counter value: {current}</p>
      <p>Is the counter paused? {isPaused ? "Yes" : "No"}</p>
      <p>Has the counter over? {isOver ? "Yes" : "No"}</p>
      <p>Current days: {currentDays}</p>
      <p>Current hours: {currentHours}</p>
      <p>Current minutes: {currentMinutes}</p>
      <p>Current seconds: {currentSeconds}</p>
      <p>Elapsed seconds: {elapsedSeconds}</p>
      <button onClick={pause}>Pause</button>
      <button onClick={play}>Play</button>
      <button onClick={reset}>Reset</button>
      <button onClick={togglePause}>Toggle Pause</button>
    </div>
  );
};

export default App;

Use cases

Here are some use cases where this React hook is useful:

  • Tracking time elapsed during a coding session in a developer productivity tool
  • Creating a stopwatch feature for tracking workout durations in a fitness tracking app