Stopwatch with External React State
Stopped
elapsed0.0s
import { createMachine, defineStates, assignEventApi } from "matchina";import { tickEffect } from "../lib/tick-effect";
export const createStopwatchMachine = ( elapsed: number, setElapsed: (elapsed: number) => void) => { const effects = { clear: () => setElapsed(0), run: () => { let lastTick = Date.now(); return tickEffect(() => { const now = Date.now(); setElapsed(stopwatch.elapsed + now - lastTick); lastTick = now; }); }, };
const states = defineStates({ Stopped: { effects: [effects.clear] }, Ticking: { effects: [effects.run] }, Suspended: {}, });
const stopwatch = Object.assign( assignEventApi( createMachine( states, { Stopped: { start: "Ticking", }, Ticking: { stop: "Stopped", suspend: "Suspended", clear: "Ticking", }, Suspended: { stop: "Stopped", resume: "Ticking", clear: "Suspended", }, }, "Stopped" ) ), { elapsed, effects, } );
return stopwatch;};import { useMachine } from "matchina/react";import { useEffect, useState } from "react";import { createStopwatchMachine } from "./machine";
export const useStopwatch = () => { const [elapsed, setElapsed] = useState(0); const [stopwatch] = useState(() => createStopwatchMachine(elapsed, setElapsed) );
useMachine(stopwatch); const state = stopwatch.getState(); useEffect(() => { const fns: (() => (() => void) | void)[] = (state.data as any)?.effects ?? []; const cleanups = fns.map((fn) => fn()).filter(Boolean) as (() => void)[]; return () => cleanups.forEach((fn) => fn()); }, [state]); stopwatch.elapsed = elapsed; return stopwatch;};export { StopwatchView } from "../stopwatch-common/StopwatchView";import { StopwatchView } from "./StopwatchView";import { useStopwatch } from "./useStopwatch";
export function Stopwatch() { const stopwatch = useStopwatch(); return <StopwatchView machine={stopwatch as any} />;}