State

useCountdown

Self-stopping countdown or count-up timer.

Demo

10

Usage

use-countdown.tsxtsx
import { useCountdown } from "hookli";

export function Demo() {
  const [count, { startCountdown, stopCountdown, resetCountdown }] = useCountdown({
    countStart: 10,
    intervalMs: 1000,
  });

  return (
    <div>
      <p>{count}</p>
      <button onClick={startCountdown}>Start</button>
      <button onClick={stopCountdown}>Pause</button>
      <button onClick={resetCountdown}>Reset</button>
    </div>
  );
}

API

useCountdown.d.tsts
useCountdown(options: UseCountdownOptions): [number, UseCountdownActions]

Parameters

NameTypeDefaultDescription
optionsUseCountdownOptionsConfigures the timer — start value, tick interval, direction and stop value. See below.

Returns

NameTypeDescription
[0] countnumberThe current count. Ticks by ±1 every intervalMs while running.
[1] actionsUseCountdownActionsStart, pause and reset controls — see below.

UseCountdownOptions

The single options argument.

NameTypeDescription
countStartnumberThe value the countdown starts from.
intervalMsnumberMilliseconds between ticks. Defaults to 1000.
isIncrementbooleanCount up instead of down. Defaults to false.
countStopnumberThe value at which the timer stops itself. Defaults to 0.

UseCountdownActions

The second tuple element — the timer controls.

NameTypeDescription
startCountdown() => voidStarts (or resumes) the timer.
stopCountdown() => voidPauses the timer without resetting the count.
resetCountdown() => voidStops the timer and resets the count to countStart.

Hook

The implementation, straight from hookli. Copy it, or install the package.

src/hooks/use-countdown/use-countdown.tstsx
import { useCallback, useEffect, useRef, useState } from "react";

export interface UseCountdownOptions {
  countStart: number;
  intervalMs?: number;
  isIncrement?: boolean;
  countStop?: number;
}

export interface UseCountdownActions {
  startCountdown: () => void;
  stopCountdown: () => void;
  resetCountdown: () => void;
}

export const useCountdown = ({
  countStart,
  intervalMs = 1000,
  isIncrement = false,
  countStop = 0,
}: UseCountdownOptions): [number, UseCountdownActions] => {
  const [count, setCount] = useState(countStart);
  const [isRunning, setIsRunning] = useState(false);

  const startCountdown = useCallback(() => setIsRunning(true), []);
  const stopCountdown = useCallback(() => setIsRunning(false), []);
  const resetCountdown = useCallback(() => {
    setIsRunning(false);
    setCount(countStart);
  }, [countStart]);

  const tick = useRef(() => {});
  tick.current = () => {
    setCount((prev) => (isIncrement ? prev + 1 : prev - 1));
  };

  useEffect(() => {
    if (isRunning && count === countStop) {
      setIsRunning(false);
    }
  }, [count, countStop, isRunning]);

  useEffect(() => {
    if (!isRunning) return;
    const id = setInterval(() => tick.current(), intervalMs);
    return () => clearInterval(id);
  }, [isRunning, intervalMs]);

  return [count, { startCountdown, stopCountdown, resetCountdown }];
};

View source on GitHub

Related hooks