Stopwatch with React State and Effects
Interactive Stopwatch Example
Section titled “Interactive Stopwatch Example”About This Example
Section titled “About This Example”This example demonstrates a React-first implementation using a declarative state machine:
- Uses React’s native state management and effects
- Encapsulates all state machine logic in a custom hook
- Simple, idiomatic React code with clear patterns
TheuseStopwatch hook relies on React for both its state management and effects, making it familiar to React developers.
Machine Code
Section titled “Machine Code”import{defineStates,createMachine,assignEventApi}from"matchina";import{useMachine}from"matchina/react";import{useState,useMemo,useEffect}from"react";import{tickEffect}from"../lib/tick-effect";exportfunctionuseStopwatch(){const[elapsed,setElapsed]=useState(0);consteffects=useMemo(()=> ({run:()=>{letlastTick=Date.now();returntickEffect(()=>{constnow=Date.now();setElapsed(stopwatch.elapsed+now-lastTick);lastTick=now;});},clear:()=>{setElapsed(0);},}),[]);// Define the state machineconststopwatch=useMemo(()=>{// Define states using defineStatesconststates=defineStates({Stopped:{},Ticking:{},Suspended:{},});// Create the base machine with states, transitions, and initial stateconstbaseMachine=createMachine(states,{Stopped:{start:"Ticking",},Ticking:{stop:"Stopped",suspend:"Suspended",clear:"Ticking",},Suspended:{stop:"Stopped",resume:"Ticking",clear:"Suspended",},},"Stopped");//Use assignEventApi to enhance the machine with utility methodsreturnObject.assign(assignEventApi(baseMachine),{elapsed:elapsed,setElapsed:setElapsed,});}, []);stopwatch.elapsed=elapsed;useMachine(stopwatch);useEffect(()=>{// if (stopwatch.changeProperty.type === "clear") {// effects.clear();// }returnstopwatch.getState().match({Ticking:effects.run,Stopped:()=>effects.clear,},false);}, [stopwatch.getState()]);returnstopwatch;}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{MachineExampleWithChart}from"@components/MachineExampleWithChart";import{StopwatchView}from"./StopwatchView";import{useStopwatch}from"./useStopwatch";// Main export for importing in MDX documentationexportdefaultfunctionStopwatchExample(){conststopwatch=useStopwatch();return (<MachineExampleWithChartmachine={stopwatch}AppView={StopwatchView}showRawState={true}/>);}exportfunctionStopwatch(){conststopwatch=useStopwatch();return<StopwatchViewmachine={stopwatchasany} />;}