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.
function useCountdown(
initialCount: number,
intervalMs?: number, // defaults to 1000
): [
count: number,
controls: { start: () => void; pause: () => void; reset: () => void },
];
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
count starts at initialCount and does not move until start() is called. Before the first start(), no timer runs.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.initialCount or intervalMs mid-countdown — assume they're stable for a given timer.