Skip to content

Union Machines

Compose a machine out of matchboxes:

52 lines, 1040 chars
const states = matchboxFactory(
  {
    Idle: () => ({}),
    Done: (x: number) => ({ result: x }),
  },
  "key",
);
type State = MemberOf<typeof states>;

const events = matchboxFactory(
  {
    execute: (x: number) => x,
  },
  "type",
);
type Event = MemberOf<typeof events>;

const transition = (state: State, event: Event) =>
  state.match({
    Idle: () =>
      event.match({
        execute: (x) => states.Done(x),
      }),
    Done: () => state,
  });

function createMachine(initialState: State) {
  let currentState = initialState;
  return {
    getState: () => currentState,
    send(event: Event) {
      currentState = transition(currentState, event);
    },
  };
}

// Usage
const machine = createMachine(states.Idle());
machine.send(events.execute(123));
console.log(machine.getState().key);

const state = machine.getState();
if (state.is("Done")) {
  state.data.result = 123;
}

// OR

const result = state.as("Done").data.result;

Use matchboxes for state:

39 lines, 1064 chars
const states = defineStates({
  NOT_LOADED: () => ({}),
  LOADING: () => ({}),
  LOADED: (data: { whatever: true }) => ({ data }),
  ERROR: (error: Error) => ({ error }),
});

type DataState = FactoryState<typeof states>;

const DataComponent: React.FC = () => {
  const [state, setState] = useState<DataState>(states.NOT_LOADED());

  return (
    <div>
      {state.match({
        NOT_LOADED: () =>
          (
            <button
              onClick={() => {
                fetch("/data")
                  .then((response) => response.json())
                  .then((data) => setState(states.LOADED(data)))
                  .catch((error) => setState(states.ERROR(error)));
              }}
            >
              Load Data
            </button>
          ) as any,
        LOADING: () => "Loading...",
        LOADED: ({ data }) => JSON.stringify(data),
        ERROR: ({ error }) => `ops, ${error.message}`,
      })}
    </div>
  );
};