Solid
@watchstop/solid adapter.
Wave 1 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
}
type StopwatchBinding = {
elapsed: Accessor<number>
start: () => void
stop: () => void
reset: () => void
stopwatch: Stopwatch
}
declare function useStopwatch(options?: UseStopwatchOptions): StopwatchBindinguseStopwatch
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, start, stop, reset } = useStopwatch()
return (
<>
<p>{elapsed()} ms</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
<button onClick={reset}>Reset</button>
</>
)
}- Construction is inert. Nothing is scheduled until
start(). - A Solid component body runs once, so
start,stop, andresetare bound once and keep the same identity for the life of the component. onCleanupcallsdestroy()when the owning reactive root or component disposes, after the unsubscribe registered for the signal.elapsedis a read-only accessor.stopwatchis the owned instance, exposed for passing elsewhere; do not calldestroy()on it yourself.
Options
| Option | Type | Purpose |
|---|---|---|
clock | Clock | Use this clock instead of detectClock(). Pass createMockClock() in tests. |
Sharing one stopwatch across components
Not supported in v1. Each useStopwatch() call constructs and owns its own instance, so two components calling it get two unrelated stopwatches, and there is no supported way to bind to a stopwatch passed through props, context, or a module-level singleton.
Stopwatch is the only Store in core today, so a generic primitive for instances the component does not own would have been speculative. Tracked in issue #6, to be revisited when Countdown / Ticker land. Until then, subscribe to the shared instance directly with Stopwatch.subscribe and write into your own signal.
Contract
- The signal starts at
store.get()and is written only fromsubscribe. onCleanupunsubscribes 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. A readout that only shows whole seconds does about sixty times the work it needs.
A precision-style option that coarsens notifications is deliberately out of scope for this change and will follow separately. Until then, keep the elapsed() read inside the smallest possible reactive scope.