Skip to content

Stopwatch with React State and State Effects

This is the cleanest, shortest version of the stopwatch examples, using specialized utility hooks:

  • Uses useStateEffects and useEventTypeEffect for simplified effect management
  • Relies on React for state management like the React-first example
  • Provides a more declarative approach to handling state-specific effects

The key innovation here is using effects tied directly to states and events (like clear), which creates a more maintainable separation of concerns.

While this approach is elegant, a more idiomatic pattern would fully separate the machine definition from React into separate files, with effects specified as non-function JS values (strings or objects created by defineEffects).

import { createMachine, defineStates, assignEventApi } from "matchina";
import { useMachine } from "matchina/react";
import { useMemo, useState } from "react";
import { useEventTypeEffect, useStateEffects } from "../lib/matchina-hooks";
import { tickEffect } from "../lib/tick-effect";
export function useStopwatch() {
const [elapsed, setElapsed] = useState(0);
const effects = useMemo(
() => ({
clear: () => setElapsed(0),
run: () => {
let lastTick = Date.now();
return tickEffect(() => {
const now = Date.now();
setElapsed(stopwatch.elapsed + now - lastTick);
lastTick = now;
});
},
}),
[]
);
// Define the state machine
const stopwatch = useMemo(() => {
// Define states using defineStates
const states = defineStates({
Stopped: { effects: [effects.clear] },
Ticking: { effects: [effects.run] },
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,
});
}, []);
useMachine(stopwatch);
useStateEffects(stopwatch.getState());
useEventTypeEffect(stopwatch.getChange(), effects);
stopwatch.elapsed = elapsed;
return stopwatch;
}