Reactions
Because the machine never sees props, a callback like onOpenChange can’t fire from inside it. A reaction is how the connector bridges that gap from the outside: a [selector, callback] tuple that watches a value derived from machine state and, when it changes, calls the matching prop.
import { machine, connector, makeReaction } from '@dunky-dev/state-machine'
const m = machine({ /* ... */})
// fix the machine generics once; Value is inferred per reactionconst reaction = makeReaction<State, Context, Event, Props>()
const onOpenChange = reaction( m => m.matches('open'), // selector → Value is boolean (open, props) => props.onOpenChange?.({ open }), // callback — open is boolean)
const c = connector(m, connect, props)c.reactions = [onOpenChange]When the machine transitions closed → open, onOpenChange({ open: true }) fires automatically. The machine itself has no idea onOpenChange exists.
How it works
Section titled “How it works”The connector runs each reaction’s selector as a value-deduped select(...). When the result changes it calls the callback with:
- The new value (typed to match the selector’s return)
- The current props — so a swapped callback is always the one that fires, not a stale closure
Reactions are wired automatically off the machine’s own lifecycle (onStart / onStop) — no activation call needed. They come alive on start() and are torn down on stop().
makeReaction()
Section titled “makeReaction()”makeReaction is an inference helper. Without it, a bare array tuple lands in Reaction<…, any> and the callback’s first argument is untyped. With it, the selector’s return type flows through to the callback:
// without makeReaction — `open` is `any`c.reactions = [[m => m.matches('open'), (open, props) => props.onOpenChange?.({ open })]]
// with makeReaction — `open` is `boolean`const reaction = makeReaction<State, Context, Event, Props>()const onOpenChange = reaction( m => m.matches('open'), (open, props) => props.onOpenChange?.({ open }),)Reactions vs. target effects
Section titled “Reactions vs. target effects”| Reaction | Target effect | |
|---|---|---|
| Lives in | connector | target’s effects.ts |
| Touches platform | no | yes |
| Runs on | every target | specific target only |
| Use for | prop-callbacks (onOpenChange) | DOM listeners, RN BackHandler |