statekit • Page 10 of 14

Subscribing to events

17/10/2026 • 2 min read

subscribeEvents calls a listener for every event a machine runs, with the event's name, its arguments and how it went. It returns the function that stops listening.

import { createMachine } from "@statekit/core"

const sleep = (ms: number) =>
   new Promise((resolve) => setTimeout(resolve, ms))

const counterMachine = createMachine({
   data: { count: 0 },
   events: {
      increment: ({ draft }, by: number) => {
         draft.count += by
      },
      save: async () => {
         await sleep(100)
      },
   },
})

const stop = counterMachine.subscribeEvents(
   ([name, args, success]) => {
      console.log(name, args, success)
   }
)

counterMachine.event.increment(2)
// "increment", [2], undefined

await counterMachine.event.save()
// "save", [], undefined   when it starts
// "save", [], true        when it resolves

stop()

A sync event sends one call, with success as undefined, or false if it threw. An async event sends two: one when it starts, with undefined, and one when it settles, with true or false. The async events demo prints them as they happen.

What it's for

Code that watches a machine without being part of it:

  • logging and analytics;
  • a dev panel of events as they happen;
  • one machine reacting to another.

This site's home page does the last one. A random-number machine regenerates its number when the app machine reports it has mounted:

const randomIntMachine = createMachine({
   data: { number: 99 },
   initializer: (init) => {
      appMachine.subscribeEvents(([eventName]) => {
         if (eventName !== "mounted") return
         randomIntMachine.event.generate()
      })

      return init
   },
   events: {
      generate: ({ draft }, max = 99) => {
         draft.number = generateRandomInt(max)
      },
   },
})

The initializer runs once, in the browser, the first time a component uses the machine. See providers.

Details

  • Listeners run in the order they subscribed.
  • A listener that throws is reported with console.error. The event still finishes and the other listeners still run.
  • Calling the returned function twice is safe.
  • setData isn't an event, so it isn't reported.
  • At runtime the second element is the full argument list, as above, but the current types describe it as the first argument only. Treat it as unknown[] for now.

#javascript, #npm, #react, #readme, #typescript, #web-development

Loading comments...