Events
Events represent the things that affect the state of the machine as it runs.
Events are finite
Just like states, we know all the events when the machine is defined.
In this “connection” state-machine, the events are CONNECT
, CONNECTED
, DISCONNECTED
and ERROR
.
Finite means the machine will only respond to these events; the machine would ignore any other events.
The Event type
yay-machine events follow the standard shape, with a type: string
and optional “payload” (ie, any other properties you need).
type ConnectionEvent = | { readonly type: "CONNECT"; readonly url: string; } | { readonly type: "CONNECTED"; } | { readonly type: "DISCONNECTED"; } | { readonly type: "ERROR"; readonly error: Error; };
Sending an event to a machine
To send an event to a machine, you’ll need a running instance, then simply call it’s send()
method:
const connectionMachine = defineMachine<ConnectionState, ConnectionEvent>({ /* ... */});
const connection = connectionMachine.newInstance().start();connection.send({ type: "CONNECT", url: "ws://localhost:9999/api" });
Of course the machine is type-safe, so you’ll get an error trying to send anything else
// ❌ ts(2322): Type '"HELLO"' is not assignable to type '"CONNECT" | "CONNECTED"' | "DISCONNECTED"' | "ERROR"'.connection.send({ type: "HELLO" });
Event payload
When your events have a payload (any other properties apart from type
), you can use these to
- generate state data for the next state in a transition
- determine which conditional transition to take
type ConnectionState = | { readonly name: "disconnected" } | { readonly name: "connecting" | "connected"; readonly url: string } | { readonly name: "connectionError"; readonly errorMessage: string };
const connectionMachine = defineMachine<ConnectionState, ConnectionEvent>({ initialState: { name: "disconnected", url: "<none>" }, states: { disconnected: { on: { CONNECT: [ { to: "connectionError", when: ({ event }) => isInvalidUrl(event.url), data: ({ event }) => ({ errorMessage: `Bad url: ${event.url}` }), }, { to: "connecting", data: ({ event }) => ({ url: event.url }), }, ], }, }, connecting: { on: { ERROR: { to: "connectionError", data: ({ event }) => ({ errorMessage: String(event.error) }), }, }, }, // ... },});
const connection = connectionMachine.newInstance().start();
connection.subscribe(({ state }) => { if (state.name === "connecting") { console.log("connecting to", state.url); } if (state.name === "connectionError") { console.log("connectionFailed: %s", state.errorMessage); }});
connection.send({ type: "CONNECT", url: "ws://localhost:9999/api" });
Subscribers receive last event
When you subscribe for state changes, you might also get the event which triggered the state change.
const connection = connectionMachine.newInstance().start();connection.subscribe(({ state, event }) => { console.log( "now in state %s, triggered by", state.name, event /* ConnectionEvent | undefined */, );});
When an event triggers a state-transition, event
will be that event.
Sometimes event
will be undefined
:
- the first time the subscriber callback is called: it only receives the machine’s current state
- when the machine transitions to a new state due to an immediate (always) transition; this is an “eventless” transition