Skip to content

Stopwatch

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

This is a React-first implementation using a declarative state machine.

Nothing fancy here. Longer than the rest, but not by much.

The useStopwatch hook relies on React for its state and effects.

Full Code

72 lines, 1726 chars
import React, { useEffect, useMemo, useState } from "react";
import { matchina } from "matchina/dev/matchina";
import { useMachine } from "matchina/integrations/react";
import { StopwatchDevView, tickEffect } from "./StopwatchCommon";

function useStopwatch() {
  const [elapsed, setElapsed] = useState(0);
  const effects = useMemo(() => ({ 
    run: () => {
      let lastTick = Date.now();
      return tickEffect(() => {
        const now = Date.now();
        setElapsed(stopwatch.elapsed + now - lastTick);
        lastTick = now;
      });
    },
    clear: () => { setElapsed(0) }
  }), [])
  // Define the state machine
  const stopwatch = useMemo(() => {        
    return Object.assign(
      matchina(
        {
          Stopped: {},
          Ticking: {},
          Suspended: {},
        },
        {
          Stopped: {
            start: "Ticking",
          },
          Ticking: {
            stop: "Stopped",
            suspend: "Suspended",
            clear: 'Ticking'
          },
          Suspended: {
            stop: "Stopped",
            resume: "Ticking",
            clear: 'Suspended'
          },
        },
        "Stopped",
      ),
      {
        elapsed: elapsed,
        setElapsed: setElapsed,
      },
    );
  }, []);
  stopwatch.elapsed = elapsed;
  useMachine(stopwatch.machine);
  useEffect(() => {
    if (stopwatch.change.type === "clear") {
      effects.clear();
    }
    return stopwatch.state.match(
      {
        Ticking: effects.run,
        Stopped: () => effects.clear,
      },
      false,
    );
  }, [stopwatch.state]);
  return stopwatch;
}

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