Interpreting machines

While a "stateless" state machine/statechart is useful for flexibility, purity, and testability, in order for it to have any use in a real-life application, something needs to:

  • Keep track of the current state, and persist it
  • Execute side-effects
  • Handle delayed transitions and events
  • Communicate with external services

The interpreter is responsible for interpreting the state machine/statechart and doing all of the above - that is, parsing and executing it in a runtime environment. An interpreted, running instance of a statechart is called a service.

The XState interpreter

Since version 4.0, XState provides an (optional) interpreter that you can use to run your statecharts. The interpreter handles:

  • State transitions
  • Executing actions (side-effects)
  • Delayed events with cancellation
  • Activities (ongoing actions)
  • Invoking/spawning child statechart services
  • Support for multiple listeners for state transitions, context changes, events, etc.
  • And more!
import { Machine } from 'xstate';
import { interpret } from 'xstate/lib/interpreter';

const machine = Machine(/* machine config */);

// Interpret the machine, and add a listener for whenever a transition occurs.
const service = interpret(machine)
  .onTransition(nextState => {
    console.log(nextState.value);
  });

// Start the service
service.start();

// Send events
service.send('SOME_EVENT');

// Stop the service when you are no longer using it.
service.stop();

Custom interpreters

You may use any interpreter (or create your own) to run your state machine/statechart. Here's an example minimal implementation that demonstrates how flexible interpretation can be (despite the amount of boilerplate):

const machine = Machine(/* machine config */);

// Keep track of the current state, and start
// with the initial state
let currentState = machine.initialState;

// Keep track of the listeners
const listeners = new Set();

// Have a way of sending/dispatching events
function send(event) {
  // Remember: machine.transition() is a pure function
  currentState = machine.transition(currentState, event);

  // Get the side-effect actions to execute
  const { actions } = currentState;

  actions.forEach(action => {
    // If the action is executable, execute it
    action.exec && action.exec();
  });

  // Notify the listeners
  listeners.forEach(listener => listener(currentState));
}

function listen(listener) {
  listeners.add(listener);
}

function unlisten(listener) {
  listeners.delete(listener);
}

// Now you can listen and send events to update state
listen(state => {
  console.log(state.value);
});

send('SOME_EVENT');