Skip to content

Stopwatch using state Effects

Stopped
0s
Loading...
{
  "type": "__initialize",
  "to": {
    "data": {
      "effects": [
        null
      ]
    },
    "key": "Stopped"
  }
}

This is the cleanest, shortest version of these stopwatch examples, partly because it relies on some utility functions, useStateEffects and useEventTypeEffect.

This example is like the React-first example. It relies on React for state and effects. But it simplifies the logic by providing effects for states and events (clear).

Still this isn’t exactly an idiomatic way of separating effects. From that perspective it would be ideal to fully separate the machine definition from React, i.e. in separate files. In that case, the machine definition should specify effects as non-function JS values, i.e. strings or objects created by defineEffects. That will be the subject of the next exploration.

Code

52 lines, 1383 chars
import React, { useMemo, useState } from "react";
import { useMachine } from "matchina/integrations/react";
import { StopwatchDevView, tickEffect, useEventTypeEffect, useStateEffects } from "./StopwatchCommon";
import { matchina } from "matchina/dev/matchina";

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(() => Object.assign(matchina({
    Stopped: {  effects: [effects.clear] }, 
    Ticking: { effects: [effects.run] }, 
    Suspended: {},
  }, {
    Stopped: {
      start: 'Ticking'
    },
    Ticking: {
      stop: 'Stopped',
      suspend: 'Suspended',
      clear: 'Ticking'
    },
    Suspended: {
      stop: 'Stopped',
      resume: 'Ticking',
      clear: 'Suspended'
    }
  }, 'Stopped'), { 
    elapsed, 
  }), [])
  useMachine(stopwatch.machine)  
  useStateEffects(stopwatch.state)
  useEventTypeEffect(stopwatch.change, effects)
  stopwatch.elapsed = elapsed
  return stopwatch
}

export function Stopwatch () {
  const stopwatch = useStopwatch()
  return <StopwatchDevView stopwatch={stopwatch} />
}