Skip to content

Stopwatch React with data and transition parameters

Stopped
0s
Loading...
{
  "type": "__initialize",
  "to": {
    "data": {
      "elapsed": 0
    },
    "key": "Stopped"
  }
}

This is a function-oriented example. It is the opposite of declarative.

It also uses state data for tracking elapsed time and does not rely on React useMemo or useState for state or effects.

It is a bit more verbose, but it shows the power of using functions for state data and transition handlers.

If you’re into that sort of thing. I am.

But admittedly, it’s not as pretty, and a little harder to follow.

This example shows how we can use functions for a richer typescript experience.

Full Code

52 lines, 1589 chars
import React, { useMemo } from "react";
import { effect, enter, setup, when } from "matchina";
import { useMachine } from "matchina/integrations/react";
import { StopwatchDevView, tickEffect } from "./StopwatchCommon";
import { matchina } from "matchina/dev/matchina";

function useStopwatch() {
  // Define the state machine
  const stopwatch = useMemo(() => {
    const model = Object.assign(matchina(
      //state data creators
      {
        Stopped: () => ({ elapsed: 0 }),
        Ticking: (elapsed = 0) => ({ elapsed, at: Date.now() }),
        Suspended: (elapsed = 0) => ({ elapsed }),
      },
      // transitions
      ({ Stopped, Ticking, Suspended }) => ({
        Stopped: { start: Ticking },
        Ticking: {
          _tick: () => (ev) => Ticking(!ev ? 0 : ev?.from.data.elapsed + (Date.now() - ev?.from.data.at)),
          stop: Stopped,
          suspend: () => (ev) => Suspended(ev?.from.data.elapsed),
          clear: Ticking,
        },
        Suspended: {
          stop: Stopped,
          resume: () => (ev) => Ticking(ev?.from.data.elapsed),
          clear: Suspended,
        },
      }),
      // initial state
      ({Stopped}) => Stopped(),
    ), {
      elapsed: 0
    });
    setup(model.machine)(
      enter(when(ev => ev.to.is("Ticking"), () => tickEffect(model._tick))),
      effect(ev => { stopwatch.elapsed = ev.to.data.elapsed ?? 0 })
    )
    return model;
  }, []);
  useMachine(stopwatch.machine);
  return stopwatch;
}

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