useCountdown
zyeon/use-countdownFreeHook
A duration-based countdown state machine with start/pause/reset controls and a once-only onComplete callback.
Install it
npx shadcn@latest add https://ui.zyeon.ai/r/use-countdown.jsonSource
1"use client"23import * as React from "react"45export interface UseCountdownOptions {6 /** Start ticking immediately on mount. Defaults to false. */7 autoStart?: boolean8 /** Fires exactly once, the instant `remaining` reaches 0. `reset()` re-arms it. */9 onComplete?: () => void10}1112export interface UseCountdownResult {13 /** Seconds left. Ticks down while running, frozen while paused. */14 remaining: number15 running: boolean16 /** (Re)start ticking from the current `remaining`. No-op if already at 0. */17 start: () => void18 /** Freeze `remaining` where it is — clears the timer, doesn't just ignore it. */19 pause: () => void20 /** Stop and set `remaining` back to the current `seconds` argument; re-arms `onComplete`. */21 reset: () => void22}2324/**25 * A duration countdown state machine: starts at `seconds` and steps down one whole second at a26 * time. This is the "count a duration" hook — OTP resend cooldowns, exam timers, limited-offer27 * buttons, anything shaped like "give me N seconds". It is **not** "count down to a moment"28 * (a date and time today); that is the presentational `Countdown` component's job (see29 * `src/registry/ui/countdown.tsx`). Neither replaces the other: the component derives30 * days/hours/minutes/seconds from a `target` for calendar-style displays, while this hook is a31 * plain numeric state machine and leaves the UI entirely to the consumer.32 *33 * - **`remaining` is derived from a deadline, not decremented once per tick.** That is34 * deliberate: browsers throttle a background tab's `setInterval` down to once a minute, so a35 * decrementing implementation **under-counts** — leave for a full 60 seconds, come back, and36 * it still claims 45 to go. While running, a `deadline` is anchored and every wake-up37 * recomputes `ceil((deadline - now) / 1000)`, so no matter how long it was throttled the38 * number you return to is the real one; `visibilitychange` forces one extra recompute so you39 * don't have to wait for the next tick. The cost is that `pause()` has to **settle the40 * deadline back into a duration** before freezing (see `pause`) — the one seam between the41 * duration and instant halves.42 * - Hitting 0 stops the run (`running` goes false) and calls `onComplete` exactly once (held43 * in a latest-ref, so an inline arrow function from the consumer never rebuilds the timer);44 * `reset()` re-arms it so the next run can fire it again.45// … truncated
Files it writes
More from zyeon
All 893 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| useAsyncuse-async | zyeon | Hooks | Free | no deps | |
| useBatteryuse-battery | zyeon | Hooks | Free | no deps | |
| useBroadcastChanneluse-broadcast-channel | zyeon | Hooks | Free | no deps | |
| useClickOutsideuse-click-outside | zyeon | Hooks | Free | no deps | |
| useClipboardPasteuse-clipboard-paste | zyeon | Hooks | Free | no deps | |
| useControllableStateuse-controllable-state | zyeon | Hooks | Free | no deps | |
| useCookieuse-cookie | zyeon | Hooks | Free | no deps | |
| useCopyToClipboarduse-copy-to-clipboard | zyeon | Hooks | Free | no deps |
