<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# useTimer

A countdown timer that keeps time by the clock, not by ticks, so it stays right in a background tab, across a reload (with `timerId`) and when the screen sleeps. It re-renders four times a second while it runs, and calls `onDone` once at zero. Every `useTimer`, `Timer` and `TimerTray` with the same `timerId` shares one timer, in this tab and in others.

```tsx
import { useTimer } from "@/lib/ouro/use-timer";
```

## Example

```tsx
const timer = useTimer({ duration: 600, timerId: "pasta", onDone: () => chime() });
<Button onClick={timer.start}>Start</Button>
<p>{formatDuration(timer.remaining)}</p>
```

## Use instead when

- Use `Timer` instead for the whole control (the time, Start, Pause and the alert), and this hook for a timer of your own design.

## Parameters

| Parameter | Type |
| --- | --- |
| `{ duration, timerId, onDone, fallback }` | `TimerOptions` |

## Returns

`TimerControls`

## Types

### `TimerControls`

What `useTimer` returns.

```ts
interface TimerControls {
  status: TimerStatus;
  /**
   * Whole seconds left, rounded up: the full duration before it starts, 0 once it is done.
   */
  remaining: number;
  /** The timer's length in seconds, as it was started. */
  duration: number;
  /**
   * When it reached zero, or `null` until it does.
   */
  endedAt: number | null;
  /** Starts from the full duration, from any status. */
  start: () => void;
  /** Stops a running timer where it is. */
  pause: () => void;
  /** Carries a paused timer on from where it stopped. */
  resume: () => void;
  /** Cancels or dismisses the timer: back to `idle` at the full duration, and forgotten. */
  reset: () => void;
}
```

### `TimerDoneEvent`

What a timer's `onDone` hears when it reaches zero.

```ts
interface TimerDoneEvent {
  /**
   * When it reached zero, in milliseconds since the epoch: in the past when the page was closed at the time.
   */
  endedAt: number;
  /**
   * Whether this `useTimer` is the one on the page that should sound the alert: the first with
   * the timer's `timerId` to hear it, not counting `fallback` ones unless only those are mounted. When
   * a `Timer` and a `TimerTray` both show one timer, the `Timer` alerts, whichever mounted first.
   * A hook without `onDone` reads the timer without taking the alert.
   */
  first: boolean;
}
```

### `TimerOptions`

Options for `useTimer`.

```ts
interface TimerOptions {
  /** How long the timer runs, in seconds. */
  duration: number;
  /**
   * Keeps the timer in `localStorage` under this name, so it carries on through a reload and
   * a closed tab, and shares it with every `useTimer`, `Timer` and `TimerTray` given the same
   * `timerId`, in this tab and in others. Without it the timer lives as long as the component.
   */
  timerId?: string | undefined;
  /** Not an option. A timer is kept and shared by `timerId`: `useTimer({ duration: 600, timerId: "pasta" })`. */
  id?: { 'Use timerId: "pasta"': never };
  /**
   * Called once when the timer reaches zero, including when it ended while the page was closed.
   */
  onDone?: ((event: TimerDoneEvent) => void) | undefined;
  /**
   * For a view that watches a timer shown elsewhere, as `TimerTray` does: its `onDone` hears
   * `first: true` only while no `useTimer` without `fallback` shares the timer.
   */
  fallback?: boolean | undefined;
}
```

### `TimerStatus`

Where a timer is: not started, counting down, stopped part way, or at zero.

```ts
type TimerStatus = "idle" | "running" | "paused" | "done";
```
