useCountdownLoading saved progress…

useCountdown

Build a React hook that drives a countdown timer with imperative controls. useCountdown(initialCount, intervalMs) starts at initialCount and, once running, ticks down by one every intervalMs milliseconds until it reaches 0, where it stops — it must never go negative. The component decides when the clock runs through three controls: start, pause, and reset. This is the engine behind OTP resend timers, "sale ends in" banners, and quiz countdowns. It builds on the same ref-driven setInterval wrapper as useInterval, with an isRunning flag layered on top.

Signature

function useCountdown(
  initialCount: number,
  intervalMs?: number, // defaults to 1000
): [
  count: number,
  controls: { start: () => void; pause: () => void; reset: () => void },
];

Examples

function ResendOtp() {
  const [count, { start, pause, reset }] = useCountdown(30, 1000);

  return (
    <div>
      <span>{count > 0 ? `Resend in ${count}s` : 'Ready'}</span>
      <button onClick={start} disabled={count === 0}>Start</button>
      <button onClick={pause}>Pause</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
// useCountdown(3, 1000), then start():
// t=0s   count = 3
// t=1s   count = 2
// t=2s   count = 1
// t=3s   count = 0   ← stops here; the timer halts, never reaches -1

Notes

  • count starts at initialCount and does not move until start() is called. Before the first start(), no timer runs.
  • Stops at zero. Once count hits 0, the interval must halt. The value never goes below 0, even if a tick was about to fire.
  • pause() freezes, reset() rewinds. pause() stops ticking but leaves count where it is, so a later start() resumes from there. reset() stops ticking and restores count to initialCount.
  • intervalMs defaults to 1000. Calling useCountdown(10) ticks once per second.
  • Don't worry about changing initialCount or intervalMs mid-countdown — assume they're stable for a given timer.
Loading editor…