Watchstop
00:00.00
Frameworks

Vue

@watchstop/vue adapter.

Composables bridging Store into a shallow ref, cleaned up with onScopeDispose. @watchstop/core is a peer dependency — install both.

Exact public names

useStopwatch is the entire public API.

type UseStopwatchOptions =
  | { clock?: Clock; precisionMs?: number }
  | { stopwatch: Stopwatch }

type StopwatchBinding = {
  elapsed: Readonly<ShallowRef<number>>
  running: Readonly<ShallowRef<boolean>>
  start: () => void
  stop: () => void
  reset: () => void
  stopwatch: Stopwatch
}

declare function useStopwatch(options?: UseStopwatchOptions): StopwatchBinding

useStopwatch

useStopwatch owns a Stopwatch and its teardown, so a component that needs its own timer imports one thing and holds no instance itself.

<script setup lang="ts">
import { useStopwatch } from '@watchstop/vue'

const { elapsed, running, start, stop, reset } = useStopwatch()
</script>

<template>
  <p>{{ elapsed }} ms</p>
  <button @click="running ? stop() : start()">{{ running ? 'Stop' : 'Start' }}</button>
  <button @click="reset">Reset</button>
</template>
  • Construction is inert. Nothing is scheduled until start().
  • setup runs once, so start, stop, and reset are bound once and keep the same identity for the life of the component.
  • onScopeDispose calls destroy() when the owning effect scope (component or effectScope) stops, after the unsubscribe registered for the ref.
  • elapsed is a read-only shallow ref. stopwatch is the owned instance, exposed for passing elsewhere; do not call destroy() on it yourself.

Options

OptionTypePurpose
clockClockOwned mode: use this clock instead of detectClock(). Pass createMockClock() in tests.
precisionMsnumberOwned mode: coarsen notify cadence — see Options.
stopwatchStopwatchBorrowed mode: bind this instance; do not pass clock / precisionMs.

Sharing one stopwatch across components

Pass the same core instance into each composable:

<script setup lang="ts">
import { Stopwatch } from '@watchstop/core'
import { useStopwatch } from '@watchstop/vue'

const session = new Stopwatch()

const { elapsed, running, start, stop, reset } = useStopwatch({
  stopwatch: session,
})
</script>

The adapter never calls destroy() on a borrowed instance. Own teardown yourself when the session ends, or leave a module-level instance alive for the page lifetime.

Contract

  • The ref starts at store.get() and is written only from subscribe.
  • onScopeDispose unsubscribes when the owning effect scope stops.
  • The returned ref is read-only; controls stay on the Stopwatch.

Re-render cost

elapsed is raw milliseconds delivered at the clock's tick cadence, so anything reading the ref re-renders roughly 60 times a second under createBrowserClock. Pass precisionMs to coarsen notifies — see Options. Keep the elapsed read in a small component when you still want finer UI.

See Store and Spec.

On this page