Watchstop
00:00.00
Frameworks

Solid

@watchstop/solid adapter.

Adapter bridging Store into a signal, unsubscribed with onCleanup. @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: Accessor<number>
  running: Accessor<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.

import { useStopwatch } from '@watchstop/solid'

export function Timer() {
  const { elapsed, running, start, stop, reset } = useStopwatch()

  return (
    <>
      <p>{elapsed()} ms</p>
      <button onClick={running() ? stop : start}>{running() ? 'Stop' : 'Start'}</button>
      <button onClick={reset}>Reset</button>
    </>
  )
}
  • Construction is inert. Nothing is scheduled until start().
  • A Solid component body runs once, so start, stop, and reset are bound once and keep the same identity for the life of the component.
  • onCleanup calls destroy() when the owning reactive root or component disposes, after the unsubscribe registered for the signal.
  • elapsed is a read-only accessor. 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 hook:

import { Stopwatch } from '@watchstop/core'
import { useStopwatch } from '@watchstop/solid'

const session = new Stopwatch()

export function SessionChip() {
  const { elapsed, running, start, stop, reset } = useStopwatch({
    stopwatch: session,
  })
  // ...
}

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 signal starts at store.get() and is written only from subscribe.
  • onCleanup unsubscribes when the owning reactive root or component disposes.
  • The accessor 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 accessor updates roughly 60 times a second under createBrowserClock. Pass precisionMs to coarsen notifies — see Options. Keep the elapsed() read in a small reactive scope when you still want finer UI.

See Store and Spec.

On this page