Stopwatch with Data and Transition Functions
Interactive Stopwatch Example
Section titled “Interactive Stopwatch Example”About This Example
Section titled “About This Example”This example demonstrates a function-oriented approach to state machine implementation. It showcases:
- Using state
datafor trackingelapsedtime without React state - Function-based state transitions for rich TypeScript type inference
- Separation of machine logic and UI components
This approach is more verbose but provides stronger typing and clearer data flow between states.
Machine Code
Section titled “Machine Code”import{defineStates,createMachine,setup,enter,when,effect,assignEventApi,}from"matchina";import{tickEffect}from"../lib/tick-effect";exportconst createStopwatchMachine=()=>{// Define states using defineStatesconststates=defineStates({Stopped:()=> ({ elapsed:0}),Ticking:(elapsed=0)=> ({elapsed, at:Date.now()}),Suspended:(elapsed=0)=> ({elapsed}),});// Create the base machine with states, transitions, and initial stateconstbaseMachine=createMachine(states,{Stopped:{ start:"Ticking"},Ticking:{_tick:()=>(ev)=>states.Ticking(!ev?0:ev?.from.data.elapsed+ (Date.now()-ev?.from.data.at)),stop:"Stopped",suspend:()=>(ev)=>states.Suspended(ev?.from.data.elapsed),clear:"Ticking",},Suspended:{stop:"Stopped",resume:()=>(ev)=>states.Ticking(ev?.from.data.elapsed),clear:"Suspended",},},"Stopped");//Use assignEventApi to enhance the machine with utility methodsconstmachine=Object.assign(assignEventApi(baseMachine),{elapsed:0,});setup(machine)(enter(when((ev)=>ev.to.is("Ticking"),()=>tickEffect(machine._tick))),effect((ev)=>{machine.elapsed=ev.to.data.elapsed??0;}));returnmachine;};import{getAvailableActionsasgetStateEvents}from"matchina";importtype{Stopwatch}from"../stopwatch-common/types";import{useMachine}from"matchina/react";/*** A simple Stopwatch UI component that displays the current state, elapsed time,* and available actions based on the current state.*/exportfunctionStopwatchView({machine}:{machine:Stopwatch}){useMachine(machine);conststate=machine.getState();// Generate color class based on stateconststateColorClass=state.match({Stopped:()=>"text-red-500",Ticking:()=>"text-green-500",Suspended:()=>"text-yellow-500",});return (<divclassName="p-4 rounded border">{/* State display */}<divclassName={`inline${stateColorClass}`}>{state.key}</div>{/* Elapsed time */}<divclassName="text-4xl font-bold my-4">{(machine.elapsed/1000).toFixed(1)}s</div>{/* Action buttons */}<divclassName="flex flex-wrap items-center gap-2">{getStateEvents(machine.transitions, state.key).filter((event)=>!event.startsWith("_")).map((event)=> (<buttonclassName="px-3 py-1 rounded bg-blue-500 text-white text-sm"key={event}onClick={()=>{(machineasany)[event]();}}>{event}</button>))}</div></div>);}import{useMemo}from"react";import{StopwatchView}from"./StopwatchView";import{createStopwatchMachine}from"./machine";exportfunctionStopwatch(){conststopwatch=useMemo(createStopwatchMachine, []);return<StopwatchViewmachine={stopwatchasany} />;}exportfunctiontickEffect(tick:()=>void,interval=50){consttimer=setInterval(tick,interval);return()=>clearInterval(timer);}