Stopwatch using State Data and Hooks
Stopped
0s
Loading...
{ "type": "__initialize", "to": { "data": { "elapsed": 0 }, "key": "Stopped" } }
This example shows how state data
can be used instead of useState
. Notice that this example only uses useMemo
to create the state machine, and does not rely on useEffect
or useState
.
Here we make elapsed
a part of the state data
and use a before
setup to propagate it from state to state.
We can also specify that whenever the machine is stopped, elapsed
will be 0
;
We also use the when
helper, which tracks when a condition is entered and runs entry and exit handlers. With this we can use when
as an alternative to useEffect
when we want to do some cleanup work when things change.
Full Code
58 lines,
1708 chars
import React, { useMemo } from "react";
import { before, effect, setup, when } from "matchina";
import { useMachine } from "matchina/integrations/react";
import { StopwatchDevView, tickEffect } from "./StopwatchCommon";
import { matchina } from "matchina/dev/matchina";
export function useStopwatch() {
// Define the state machine
const stopwatch = useMemo(() => {
const model = Object.assign(matchina(
//state data creators
{
Stopped: () => ({ elapsed: 0 }),
Ticking: (elapsed: number = 0) => ({ at: Date.now(), elapsed }),
Suspended: (elapsed = 0) => ({ elapsed }),
} as const,
// transitions
{
Stopped: { start: "Ticking" },
Ticking: {
_tick: 'Ticking',
stop: "Stopped",
suspend: 'Suspended',
clear: "Ticking",
},
Suspended: {
stop: "Stopped",
resume: 'Ticking',
clear: "Suspended",
},
},
// initial state
"Stopped",
), {
elapsed: 0
})
setup(model.machine)(
before((ev) => {
ev.to.data.elapsed = ev.match({
stop: () => 0,
clear: () => 0,
_: () => ev.from.data.elapsed,
_tick: () => ev.from.data.elapsed + (Date.now() - ev.from.as('Ticking').data.at),
}, false)
}),
effect(when((ev) => ev.to.is("Ticking"), () => tickEffect(stopwatch._tick))),
effect((ev) => { model.elapsed = ev.to.data.elapsed }),
);
return model
}, []);
useMachine(stopwatch.machine);
return stopwatch;
}
export function Stopwatch () {
const stopwatch = useStopwatch()
return <StopwatchDevView stopwatch={stopwatch} />
}