I have a simple machine:
import { createMachine } from 'xstate'
export const machine = createMachine(
{
predictableActionArguments: true,
schema: {
context: {} as { elapsed: number },
events: {} as { type: 'AT_SAFE_ZONE' } | { type: 'WALKING' },
},
initial: 'a',
states: {
a: {
on: {
WALKING: {
target: 'b',
},
},
},
b: {
on: {
AT_SAFE_ZONE: {
target: 'a',
},
},
exit: ['calculateWalk'],
},
},
},
{
actions: {
calculateWalk: async (context, event) => {
console.log('>>> calculateWalk', context, event)
},
},
}
)
And when I use it:
const { initialState } = machine
const nextState = machine.transition(initialState, { type: 'WALKING' })
console.log(nextState.value)
const nextState2 = machine.transition(nextState, { type: 'AT_SAFE_ZONE' })
I can see the states changing from a
to b
then to a
again, but the action is never called.
Context: I'm testing the machine on vitest, I can see all logs except from the function calculateWalk
. I tried many things: with and without array of actions, inside and outside of the "on" block... Nothing worked.
The transition function of a FSM can be understood as a pure function that takes in some state and an event and computes the next state as a return value if the machine defines a transition for this combination of input state and event. It's like a mathematical calculation and truthful to the formal definition of a finite state machine.
const nextState = machine.transition('some state', { type: 'SOME_EVENT' })
It's the same in XState: the machine's transition function is a pure function that computes the next State
object (see State API) without performing any actions[1] of the machine. While the old XState docs show some examples using this function, it's rarely used in practice.
Instead, in most cases you run the machine as a service which also executes its actions. You can do this with the interpret function or helper functions like useMachine()
from one of the framework-specific packages and then sending events to the machine with send()
.
import { createMachine, interpret } from 'xstate';
const machine = createMachine({/* your machine definition */});
const actor = interpret(machine).start();
actor.send({ type: 'WALKING' })
actor.send({ type: 'AT_SAFE_ZONE' })
[1] By the way, createMachine()
runs the entry action of the initial state if an entry action is defined.