Hierarchical States
Overview
Section titled “Overview”Any state can own a child FSM by setting _child to an FSM instance. While the parent is in that state, inputs dispatched via handle() are checked against the child first using canHandle(). If the child can handle the input, it’s forwarded. If not, the parent’s own state handlers get it. If the child itself sends an input it can’t handle (e.g. from _onEnter), that fires nohandler on the child and bubbles up to the parent.
Setting up a child
Section titled “Setting up a child”Assign any createFsm (or createBehavioralFsm) instance to the _child property of a state definition. The parent delegates to it automatically — no other wiring required.
Input delegation
Section titled “Input delegation”When handle() is called on the parent, the dispatch order is:
- Input arrives at the parent via
handle() - Parent checks if the current state has a
_child - If yes, parent calls
canHandle()on the child - If the child can handle it, the input is forwarded to the child’s
handle()— done - If the child can’t handle it, the parent’s own state handlers get the input
The child gets first shot via canHandle(), and the parent’s handlers act as a fallback. Separately, if the child sends itself an input it can’t handle (e.g. inside _onEnter), that fires nohandler on the child and bubbles up to the parent — this is how the traffic intersection’s phaseComplete input reaches the parent from the child’s red state.
compositeState()
Section titled “compositeState()”compositeState() returns the full state path as a dot-delimited string, walking down through active child FSMs. If the parent is in "active" and the child is in "uploading", you get "active.uploading".
This is useful for driving UIs from a single string — one compositeState() call tells you the full picture without interrogating multiple FSMs.
Nesting is unbounded. A child can itself have a _child, and compositeState() walks the whole chain: "stateA.stateB.stateC".
Child auto-reset
Section titled “Child auto-reset”When the parent transitions into a state that owns _child, machina automatically calls reset() on the child, returning it to its initialState. This happens after _onEnter and the transitioned event, but before deferred queue processing.
Re-entering a parent state always starts the child fresh. There is no way to “resume” a child mid-way — if you need that, model it explicitly in your state machine.
Disposal
Section titled “Disposal”dispose() on the parent cascades to child FSMs by default. If the same child FSM appears in multiple states, it is only disposed once.
Pass { preserveChildren: true } to skip child disposal and keep the child FSM running independently:
Full example
Section titled “Full example”The uploader above, assembled and exercised:
For a more complex real-world case — two independent child FSM instances, input bubbling across phases, defer() for pedestrian requests, and child auto-reset driving a full traffic signal cycle — see the Traffic Intersection example.