Skip to content

Stopwatch State Machine

This example demonstrates a stopwatch state machine with start, stop, and reset functionality. It shows how to manage time-based state and lifecycle hooks for continuous updates.

The stopwatch has three states:

  • Stopped: The initial state with an elapsed time of 0
  • Ticking: The running state that updates elapsed time
  • Suspended: A paused state that preserves elapsed time

The machine uses lifecycle hooks to implement the timer functionality, automatically updating the elapsed time when in the Ticking state.

import {
defineStates,
createMachine,
setup,
enter,
when,
effect,
assignEventApi,
before,
} from "matchina";
import { tickEffect } from "../lib/tick-effect";
export const createStopwatchMachine = () => {
// Define states using defineStates
const states = defineStates({
Stopped: (elapsed = 0) => ({ elapsed }),
Ticking: (elapsed = 0) => ({ elapsed, at: Date.now() }),
Suspended: (elapsed = 0) => ({ elapsed }),
});
// Create the base machine with states, transitions, and initial state
const baseMachine = createMachine(
states,
{
Stopped: { start: "Ticking" },
Ticking: {
_tick: "Ticking",
stop: "Stopped",
suspend: "Suspended",
clear: "Ticking",
},
Suspended: {
stop: "Stopped",
resume: "Ticking",
clear: "Suspended",
},
},
states.Stopped()
);
//Use assignEventApi to enhance the machine with utility methods
const machine = Object.assign(assignEventApi(baseMachine), {
elapsed: 0,
});
// Setup hooks directly on the machine (no need for .machine with assignEventApi)
setup(machine)(
// Before transition handler
before((ev) => {
if (ev.type === "_tick" && ev.from.is("Ticking")) {
ev.to.data.elapsed =
ev.from.data.elapsed + (Date.now() - ev.from.data.at);
}
if (ev.type === "clear") {
ev.to.data.elapsed = 0;
}
return () => {};
}),
// Effect when entering Ticking state
enter(
when(
(ev) => ev.to.is("Ticking"),
() => tickEffect(machine._tick)
)
),
// Effect for all transitions - update elapsed field
effect((ev) => {
machine.elapsed = ev?.to.data.elapsed ?? 0;
})
);
return machine;
};