Skip to content

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

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