Skip to content

Stopwatch with React State and Effects

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

The useStopwatch hook relies on React for both its state management and effects, making it familiar to React developers.

import { defineStates, createMachine, assignEventApi } from "matchina";
import { useMachine } from "matchina/react";
import { useState, useMemo, useEffect } from "react";
import { tickEffect } from "../lib/tick-effect";
export function useStopwatch() {
const [elapsed, setElapsed] = useState(0);
const effects = useMemo(
() => ({
run: () => {
let lastTick = Date.now();
return tickEffect(() => {
const now = Date.now();
setElapsed(stopwatch.elapsed + now - lastTick);
lastTick = now;
});
},
clear: () => {
setElapsed(0);
},
}),
[]
);
// Define the state machine
const stopwatch = useMemo(() => {
// Define states using defineStates
const states = defineStates({
Stopped: {},
Ticking: {},
Suspended: {},
});
// Create the base machine with states, transitions, and initial state
const baseMachine = 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 methods
return Object.assign(assignEventApi(baseMachine), {
elapsed: elapsed,
setElapsed: setElapsed,
});
}, []);
stopwatch.elapsed = elapsed;
useMachine(stopwatch);
useEffect(() => {
// if (stopwatch.changeProperty.type === "clear") {
// effects.clear();
// }
return stopwatch.getState().match(
{
Ticking: effects.run,
Stopped: () => effects.clear,
},
false
);
}, [stopwatch.getState()]);
return stopwatch;
}