Skip to content

Factory Machines

Factory Machines combine state factories with transitions to create complete state machines with type-safe operations. They extend the Matchbox pattern to provide:

  1. Type-safe state creation with proper parameter inference
  2. Type-safe transitions with smart parameter types
  3. State data preservation when transitioning between states
  4. Lifecycle hooks for intercepting and reacting to state changes
const {(getState, send)} = fsm(states, transitions, initialState)

Creating Factory Machines and Using matchina

Section titled “Creating Factory Machines and Using matchina”

To create a state machine, always start by defining your states using defineStates:

const states = defineStates({
S1: () => ({}),
S2: () => ({}),
});

You can then create and use a machine in several ways:

// Basic factory machine
const { getState, send } = createMachine(
states,
{ S1: { e1: "S2" }, S2: { e2: "S1" } },
states.S1()
);
// With matchina (zen wrapper for ergonomic API)
const { getState, send, e1, e2 } = matchina(
states,
{ S1: { e1: "S2" }, S2: { e2: "S1" } },
states.S1()
);
// Or, equivalently, assignEventApi-wrapped factory machine
const { getState, send, e1, e2 } = assignEventApi(
createMachine(states, { S1: { e1: "S2" }, S2: { e2: "S1" } }, states.S1())
);
  • createMachine gives you .getState() and .send(type, ...params).
  • matchina (or assignEventApi(createMachine(...))) gives you .getState(), .send(type, ...params), and direct methods for each event (e.g. .e1(), .e2()).
  • The APIs are otherwise equivalent; matchina is just a convenience wrapper.

Transition logic determines:

  • Which exit states are possible for each entry state and event
  • What parameters are required to send those events
  • How state data is propagated or explicitly created during transitions

A transition handler can take one of these forms:

  1. String state key: If your transition does not accept parameters, simply return the destination state key as a string. Data is preserved automatically if compatible.
    search: "Loading";
  2. Function with parameters: If your transition accepts parameters, return a state instance or a function that creates a state instance based on the event.
    • Return a state instance directly:
      search: (query: string) => TaskStates.Loading(query);
    • Or, for advanced cases, return a function that receives the event object ({ type, from, to }) and returns a state instance:
      success: (results: string[]) => (event) =>
      TaskStates.Success(event.from.data.query, results);

Summary Table:

FormExampleUse Case
String (state key)pause: "Paused"No params, simple state change
Functionsearch: (q) => TaskStates.Loading(q)Accepts params, returns state instance
Event functionsuccess: (r) => (ev) => ...Needs event context for state creation

  • With createMachine, trigger transitions by calling:

    machine.send(type, ...params);
  • With matchina or assignEventApi, you can also call event methods directly:

    machine.e1(...params);
  • type is the event name (string)

  • ...params are the parameters for the transition (if any)

Get the current state for inspection:

const state = machine.getState();
console.log(state.key); // Current state key

Pattern matching on state is only available if your state factory supports it (e.g. if you used a matchbox factory). For more, see the matchbox documentation.


Now that you understand Factory Machines and matchina, explore these related guides: