Skip to content

beezwax/micro-machines

Repository files navigation

Micromachines

Minimalistic state machines for TypeScript and React.

Installation

$ npm i beezwax/micro-machines

Quickstart

import { createMachine } from "micro-machines/react";

type States = "INITIAL" | "FINAL";

interface Context {
  people: Person[];
}

const peopleMachine = () =>
  createMachine<Context, States>((transition) => ({
    context: {
      people: [],
    },
    initial: "INITIAL",
    final: "FINAL",
    states: {
      async INITIAL() {
        const people = await fetchPeople();
        await transition("FINAL", { people });
      },
      FINAL: undefined,
    },
  }));

A machine is a set of states, transitions between those states, and some data called context. In the above example we create a machine that fetches people though a fictional People API.

State machines make it very easy to run a process and track its progress:

import { useMachine } from 'micro-machines/react'

const StartWithActionDemo = () => {
  const { start, state, context } = useMachine(peopleMachine);

  return (
    <div>
      <h3>This state machine executes after the button is pressed</h3>
      <p>State: {state}</p>

      {context?.people.map((person) => (
        <div key={person.id}>
          <p>Name: {person.name}</p>
          <p>Age: {person.age}</p>
        </div>
      ))}

      <button onClick={start}>Start</button>
    </div>
  );
};

Machines can also start automatically, convenient for some React use-cases:

const AutoStartDemo = () => {
  const { state, context } = useAutoStartingMachine(peopleMachine);

  return (
    <div>
      <h3>This state machine runs as soon a it's rendered</h3>
      <p>State: {state}</p>
      {context?.people.map((person) => (
        <div key={person.id}>
          <p>Name: {person.name}</p>
          <p>Age: {person.age}</p>
        </div>
      ))}
    </div>
  );
};

To handle errors, simply go to an error state:

const peopleMachine = () =>
  createMachine<Context, States>((transition) => ({
    context: {
      people: [],
      error?: Error,
    },
    initial: "INITIAL",
    final: "FINAL",
    states: {
      async INITIAL() {
        // IMPORTANT: Note that a non-terminal state (a state that's not undefined)
        // must always transition to another state, otherwise the machine will be
        // stuck in that state, and will not terminate.
        const people = await fetchPeople();
        try {
          await transition("FINAL", { people });
        } catch (error) {
          await transition("ERROR", { error: new Error("Something went wrong!") })
        }
      },
      FINAL: undefined,
      ERROR: undefined,
    },
  }));

If the machine is not in a state defined in final, then machine.success will be false. You can access success directly in the React hook. You also get a terminated boolean to know whether the machine terminated or not.

const { start, state, context, success, terminated } =
  useMachine(peopleMachine);

Composing Machines

Machines can call other machines using runMachine:

const myMachine = createMachine<MyContext, MyStates>((transition) => ({
  context: { value: 0 },
  initial: "INITIAL",
  final: "FINAL",
  states: {
    async INITIAL() {
      // We use `runMachine` to execute a machine, and transition accordingly
      await runMachine({
        machine: incrementCounterMachine,
        context: { count: 10 }, // Optional initial context
        async success({ count }) {
          await transition("FINAL", { value: count });
        },
        async failure() {
          await transition("ERROR");
        },
      });
    },
    FINAL: undefined,
    ERROR: undefined,
  },
}));

About

Minimalistic state machines for TypeScript and React

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published