Connector
connector keeps a pure connect function live: it memoizes the snapshot, makes props a reactive input, and wires reactions automatically.
import { machine, connector } from '@dunky-dev/state-machine'
const m = machine({ /* ... */})
const connect = ({ state, send }) => ({ isOpen: state === 'open', triggerProps: { onPress: () => send({ type: 'toggle' }) },})
const c = connector(m, connect, /* initial props */ {})
c.snapshot // memoized view API — stable identity until something changesc.subscribe(render) // coarse — wake the view on any changec.select // forwarded fine-grained pathc.setProps(props) // push new props in — shallow-dedup'dThe machine never sees props
Section titled “The machine never sees props”A machine here is pure behavior — it has no props argument. Every job a prop does lands at the edge, never the machine:
| A prop that… | …goes here |
|---|---|
| configures behavior | seeded into context once (updated via setContext) |
| fires a callback | a reaction on the connector |
| is controlled state | resolved into initial state before machine() is built |
This is what keeps the same machine running byte-for-byte identically on every target.
connect function
Section titled “connect function”connect is a pure function — it maps a machine snapshot to a view-facing API object. It receives { state, context, computed, send, matches, hasTag } and returns whatever the view needs:
const connect = ({ state, context, send, matches }) => ({ isOpen: matches('open'), value: context.value, triggerProps: { onPress: () => send({ type: 'toggle' }) }, panelProps: { role: 'region', hidden: !matches('open') },})Returning abstract bindings (onPress, role) instead of platform props (onClick, aria-*) keeps connect renderer-blind. A per-target normalize step translates them into real props.
setProps
Section titled “setProps”Props are a reactive input to the connector. setProps is shallow-dedup’d — passing a fresh-but-equal props object (common when a host rebuilds props every render) is a no-op and won’t recompute the snapshot or wake subscribers.
Snapshot identity
Section titled “Snapshot identity”c.snapshot only changes identity when the machine or props change. It drops straight into useSyncExternalStore(c.subscribe, () => c.snapshot) without tearing or infinite-loop pitfalls.