Skip to content

Subscriptions

Two ways to observe a machine from the outside: coarse subscribe for any change, fine-grained select for a specific slice.

Fires on any state or context change. The standard bridge for useSyncExternalStore:

const off = m.subscribe(() => rerender())
off() // unsubscribe

Narrows to a slice and fires only when that slice’s value changes. A subscriber wakes only for the data it reads:

// named field — typed and autocompleted
m.select.context('count').subscribe(n => console.log('count is now', n))
m.select.computed('isEmpty').subscribe(empty => toggle(empty))
m.select.state().subscribe(s => console.log('state →', s))

For a derived or composite slice, pass a selector function:

const view = m.select(() => ({
open: m.matches('open'),
count: m.context.count,
}))
// optional equality — skip re-renders when neither field changed
view.subscribe(render, (a, b) => a.open === b.open && a.count === b.count)
view.value // read the current value without subscribing

A select re-evaluates on any machine change but only calls its listeners when the selected value actually changes. This makes it safe to use directly in React’s useSyncExternalStore — no infinite-loop risk from returning a fresh object on every call.

// React bridge pattern
const snapshot = useSyncExternalStore(
cb => m.select.context('count').subscribe(cb),
() => m.context.count,
)