Examples
Copy-and-paste snippets for @timekeeper-countdown/react. When new framework adapters ship we will add dedicated examples for them as well.
Basic Timer Card
tsx
import { useCountdown } from '@timekeeper-countdown/react';
import { formatTime } from '@timekeeper-countdown/core/format';
export function TimerCard() {
const countdown = useCountdown(60);
const clock = formatTime(countdown.snapshot);
return (
<div>
<p>
{clock.minutes}:{clock.seconds}
</p>
<button onClick={countdown.start} disabled={countdown.isRunning}>
Start
</button>
<button onClick={countdown.pause} disabled={!countdown.isRunning}>
Pause
</button>
<button onClick={countdown.reset}>Reset</button>
</div>
);
}Form-Controlled Duration
tsx
import { useState } from 'react';
import { useCountdown } from '@timekeeper-countdown/react';
function AdjustableCountdown() {
const [seconds, setSeconds] = useState(150);
const countdown = useCountdown(seconds, { autoStart: false });
return (
<section>
<label>
Seconds
<input type="number" value={seconds} onChange={event => setSeconds(Number(event.target.value) || 0)} />
</label>
<div>
<button onClick={countdown.start}>Start</button>
<button onClick={countdown.pause}>Pause</button>
<button onClick={() => countdown.reset(seconds)}>Apply</button>
</div>
<p>{countdown.totalSeconds}s remaining</p>
</section>
);
}Auto-Chaining Phases
tsx
import { useEffect } from 'react';
import { useCountdown } from '@timekeeper-countdown/react';
function TwoStageFlow() {
const intro = useCountdown(15, { autoStart: true });
const main = useCountdown(90);
const { isCompleted: introCompleted } = intro;
const { isRunning: mainRunning, start: startMain } = main;
useEffect(() => {
if (introCompleted && !mainRunning) {
startMain();
}
}, [introCompleted, mainRunning, startMain]);
return (
<div>
<h3>Intro: {intro.totalSeconds}s</h3>
<h3>Main Session: {main.totalSeconds}s</h3>
</div>
);
}Testing with @testing-library/react
tsx
import { useMemo } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useCountdown } from '@timekeeper-countdown/react';
import { createFakeTimeProvider, toTimeProvider } from '@timekeeper-countdown/core/testing-utils';
function InspectableTimer() {
const fake = useMemo(() => createFakeTimeProvider({ startMs: 0 }), []);
const countdown = useCountdown(5, {
timeProvider: toTimeProvider(fake),
tickIntervalMs: 5,
});
return (
<div>
<output>{countdown.totalSeconds}</output>
<button onClick={() => fake.advance(1000)}>Advance</button>
</div>
);
}
it('advances when the fake clock moves', async () => {
render(<InspectableTimer />);
await userEvent.click(screen.getByRole('button', { name: /advance/i }));
expect(screen.getByText('4')).toBeInTheDocument();
});Note: When testing with
renderHookor custom render functions that use Vitest fake timers (vi.useFakeTimers()), you may need to advance both the fake time provider AND Vitest's internal timers. The fake provider controls what time the engine reads; Vitest fake timers control whensetIntervalcallbacks fire.tsvi.useFakeTimers({ toFake: ['setTimeout', 'setInterval'] }); const fake = createFakeTimeProvider({ startMs: 0 }); // Inside your test: act(() => { fake.advance(1000); // engine reads 1s elapsed vi.advanceTimersByTime(1000); // triggers setInterval callbacks });
Testing with Core Utilities
Snapshot fabrication for unit tests
ts
import {
buildSnapshot,
buildSnapshotSequence,
assertSnapshotState,
assertSnapshotCompleted,
assertRemainingSeconds,
TimerState,
} from '@timekeeper-countdown/core/testing-utils';
// Create an isolated snapshot
const idle = buildSnapshot({ totalSeconds: 60 });
assertSnapshotState(idle, TimerState.IDLE);
// Create a completed snapshot
const done = buildSnapshot({ totalSeconds: 0, state: TimerState.STOPPED });
assertSnapshotCompleted(done);
// Verify with tolerance
const mid = buildSnapshot({ totalSeconds: 30, state: TimerState.RUNNING });
assertRemainingSeconds(mid, 30);
// Generate a sequence
const sequence = buildSnapshotSequence({ totalSeconds: 10, step: 5, count: 3 });
// [10s RUNNING, 5s RUNNING, 0s STOPPED]Deterministic test with engine (plain core, no React)
ts
import { CountdownEngine } from '@timekeeper-countdown/core';
import {
createFakeTimeProvider,
toTimeProvider,
assertRemainingSeconds,
assertSnapshotCompleted,
TimerState,
} from '@timekeeper-countdown/core/testing-utils';
const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
const engine = CountdownEngine(5, {
timeProvider: toTimeProvider(fake),
tickIntervalMs: 5,
});
engine.start();
fake.advance(3000); // advance 3 seconds
assertRemainingSeconds(engine.getSnapshot(), 2);
fake.advance(2000); // advance 2 more seconds
assertSnapshotCompleted(engine.getSnapshot());Coming Soon
Adapters for Angular, Vue, Svelte, and a vanilla bundle are in development. As they land, this page will grow with side-by-side examples so you can port patterns across frameworks with minimal effort.