Stopwatch with React state and useLifecycle
Stopped
0s
Loading...
{ "type": "__initialize", "to": { "data": {}, "key": "Stopped" } }
This version uses onLifecycle
instead of useEffect
.
It is somewhat verbose, and mainly serves to show how the onLifecycle
hook could be used here.
One nice thing about onLifecycle
is the autocomplete experience for the developer that makes it easy to explore the available extensions for each state and event.
And within a given state or event config, the event will be constrained to that state and/or event. For example { Rejected: { enter: ev => { ev.data.error } } }
would autocomplete ev.error
that is part of the Rejected
state data
.
Full Code
63 lines,
1831 chars
import React, { useEffect, useMemo, useState } from "react";
import { onLifecycle, when } from "matchina";
import { useMachine } from "matchina/integrations/react";
import { StopwatchDevView, tickEffect } from "./StopwatchCommon";
import { matchina } from "matchina/dev/matchina";
function useStopwatch() {
const [startTime, setStartTime] = useState<number | undefined>(undefined);
const [elapsed, setElapsed] = useState(0);
// Define the state machine
const stopwatch = useMemo(() => Object.assign(matchina({
Stopped: {},
Ticking: {},
Suspended: {},
}, {
Stopped: {
start: 'Ticking'
},
Ticking: {
stop: 'Stopped',
suspend: 'Suspended',
clear: 'Ticking'
},
Suspended: {
stop: 'Stopped',
resume: 'Ticking',
clear: 'Suspended'
}
}, 'Stopped'), {
startTime,
elapsed,
}), [])
useEffect(() => onLifecycle(stopwatch.machine, {
'*': {
enter: when(ev => ev.to.is('Ticking'), () => tickEffect(() => {
setElapsed(Date.now() - (stopwatch.startTime ?? 0));
})),
on: {
start: { effect: () => { setStartTime(Date.now()) } },
clear: { effect: () => setElapsed(0) },
stop: { effect: () => setElapsed(0) },
resume: { effect: () => setStartTime(Date.now() - (stopwatch.elapsed??0)) }
}
},
Ticking: {
on: { clear: { effect() { setStartTime(Date.now()) } } },
},
Suspended: {
on: { clear: { effect () { setStartTime(undefined) } } }
},
}), [stopwatch])
useMachine(stopwatch.machine)
stopwatch.startTime = startTime
stopwatch.elapsed = elapsed
return stopwatch
}
export function Stopwatch () {
const stopwatch = useStopwatch()
return <StopwatchDevView stopwatch={stopwatch} />
}