Skip to content

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} />
}