Running machines
We’ve now covered configuring statecharts and setting up their states, transitions, and options.
Next, we need to learn how to execute our machines in code. Depending on where you’re using XState, you’ll likely need to run machines differently. We’ll start with the VanillaJS approach for now.
Interpret
The interpret
interpreter provides us with a “running” and interactive version of our machine.
import { createMachine, interpret } from 'xstate';
const machine = createMachine({});
const actor = interpret(machine).start();
In XState, we call that “running” machine an actor. The most common way to interact with an actor is to send it events:
import { createMachine, interpret } from 'xstate';
const machine = createMachine(
{
on: {
SAY_HELLO: {
actions: 'sayHello',
},
},
},
{
actions: {
sayHello: () => {
console.log('Hello!');
},
},
}
);
const actor = interpret(machine).start();
actor.send({
type: 'SAY_HELLO',
});
// Logs 'Hello!'
You can also subscribe to the actor via actor.subscribe
:
import { createMachine, interpret } from 'xstate';
const machine = createMachine(
{
on: {
SAY_HELLO: {
actions: 'sayHello',
},
},
},
{
actions: {
sayHello: () => {
console.log('Hello!');
},
},
}
);
const actor = interpret(machine).start();
// Fires whenever the state changes
const { unsubscribe } = actor.subscribe((state) => {
console.log(
// We’ll learn what goes inside here soon!
state
);
});
You can also stop the actor by running actor.stop()
which cleans up the actor and runs any relevant exit actions:
import { createMachine, interpret } from 'xstate';
const machine = createMachine(
{
on: {
SAY_HELLO: {
actions: 'sayHello',
},
},
},
{
actions: {
sayHello: () => {
console.log('Hello!');
},
},
}
);
const actor = interpret(machine).start();
actor.stop();
interpret
can run anywhere JavaScript runs, which means you can run XState in the browser, Node, Electron, React Native… anywhere!
Check out the reference docs on the interpret API for a full deep dive into everything a actor
can do.
State API
When running your machine, you’ll want to query the machine to understand which state it’s in. When you run a machine using interpret
, you can find the state as follows:
import { createMachine, interpret } from 'xstate';
const machine = createMachine({});
const actor = interpret(machine).start();
const state = actor.state;
You can also get an updated version when you subscribe to the state:
import { createMachine, interpret } from 'xstate';
const machine = createMachine({});
const actor = interpret(machine).start();
actor.subscribe((state) => {
console.log(state);
});
This State
class has a bunch of useful attributes and methods.
Context
First, you can find the state’s context using state.context
.
import { createMachine, interpret, assign } from 'xstate';
const machine = createMachine(
{
context: {
count: 0,
},
on: {
INCREMENT: {
actions: 'incrementCount',
},
},
},
{
actions: {
incrementCount: assign((context) => {
return {
count: context.count + 1,
};
}),
},
}
);
const actor = interpret(machine).start();
actor.subscribe((state) => {
console.log(state.context.count);
});
actor.send({ type: 'INCREMENT' });
state.matches
You can query which state the machine is in by running state.matches()
. state.matches()
returns a boolean depending on whether you’re in a matching state.
import { createMachine, interpret, assign } from 'xstate';
const machine = createMachine({
initial: 'toggledOff',
states: {
toggledOff: {
on: {
TOGGLE: 'toggledOn',
},
},
toggledOn: {
on: {
TOGGLE: 'toggledOff',
},
},
},
});
const actor = interpret(machine).start();
actor.subscribe((state) => {
const isToggledOn = state.matches('toggledOn');
console.log(isToggledOn); // true / false
console.log(state.value); // 'toggledOn' / 'toggledOff'
});
You can also use state.value
to check your machine’s state, but we recommend using state.matches
because of how it works with parent and child states. We’ll talk more about parent states later. Jump directly to parent and child states.
Changed
You can check if a state was changed by the most recently received event. The state was changed if:
- There was a change in
state.value
. For example, moving fromtoggledOn
totoggledOff
. - A value in context was changed.
Running machines as pure functions
You can use machine.transition
to run your machine as a pure function without executing any of its actions. machine.transition
is a pure function that takes two arguments:
state
, theState
to transition fromevent
, the event that causes the transition
machine.transition
returns a new State
resulting from taking all the transitions enabled by the current state and event.
const machine = createMachine({
/* ... */
});
const initialState = machine.initialState;
// determine next state based on current state and event
const yellowState = machine.transition(initialState, {
type: 'TIMER',
});
console.log(yellowState.value);
// => 'yellow'