Rxova
Skip to content

Machine API reference

Linear and graph factories return the same grouped base surface. Graph machines additionally expose send.

Returns the latest LinearSnapshot or GraphSnapshot. See Snapshot.

const snapshot = machine.getSnapshot();

The machine has no separate computed-state or metadata getters. Derived state and current-step metadata live in the snapshot.

Every control returns a boolean indicating whether the state change applied.

MethodBehavior
controls.start()Start an idle machine and enter its initial step.
controls.pause()Pause a running machine when no transition is pending.
controls.resume()Resume a paused machine.
controls.complete(payload?)Complete a running machine and record an outcome.
controls.terminate(payload?)Terminate from any non-terminated status and invalidate pending work.
controls.restart()Reset a completed/terminated run and start it again.
if (!machine.controls.start()) {
console.log("machine was already started");
}

start() commits the initial step before returning, then settles onEnter asynchronously. Wait until snapshot.transition.pending becomes false before issuing the first navigation. The Quickstart includes a selector-based waitUntilSettled helper.

Because autoStart defaults to true, a machine is normally already running by the time you hold it and start() returns false. It is worth calling only on one created with { autoStart: false }.

Completion is always explicit. Navigation never auto-completes.

The optional third createLinearJourney<StepId, Context, TerminationPayloads> generic groups complete and terminate payload shapes. Those shapes constrain the control arguments and narrow snapshot.machine.outcome.

All navigation methods return Promise<NavigationResult<TStepId>>.

Linear journeys may jump to any declared id. Graph journeys require an enabled outgoing transition whose destination is that id.

Section titled “navigate.goToPreviousStep(work?) / navigate.goToPreviousStep(n, work?)”

Move the timeline pointer back by at least one entry, clamping to the beginning. Optional work uses the same pre-commit contract as next navigation.

Move through an existing forward timeline entry. At the timeline tip, linear journeys move to the next declared step; graph journeys return "out-of-bounds".

await machine.navigate.goToNextStep({
run: async ({ snapshot, from, to, direction }) => {
return api.submit(snapshot.context);
},
commit: ({ result, updateContext }) => {
updateContext((context) => ({ ...context, submissionId: result.id }));
}
});

run is awaited while the source remains current. If it fails, navigation returns reason: "error". commit must be synchronous; its context updates publish atomically with movement.

Use snapshot.transition.pending as the normal UI-level loading flag. Inspect snapshot.transition for phase details and snapshot.currentStep.async for the current entry’s success or error state.

Move the pointer to the realized timeline tip.

On linear journeys, goToStepById is an ungated escape hatch for occasional direct jumps. Prefer graph mode when explicit jumps or branches are part of normal domain behavior.

type NavigationResult<TStepId extends string> =
| { readonly ok: true; readonly from: TStepId | null; readonly to: TStepId }
| {
readonly ok: false;
readonly reason: NavigationFailureReason;
readonly error?: unknown;
};

Failure reasons are error, transitioning, not-running, invalid-target, no-enabled-transition, out-of-bounds, no-op, and disposed.

Graph send selects the first enabled candidate for the current step and event type.

await machine.send("SUBMIT", { email: "ada@example.com" });
await machine.send("CANCEL");

Declared discriminated-union events produce exact payload tuples. Without a declared event union, the fallback signature is (type: string, payload?: unknown).

Synchronously replaces context and publishes a snapshot plus contextChange event.

machine.context.update((context) => ({ ...context, name: "Ada" }));

After disposal it is a no-op. The runtime does not enforce serializability, though persistence and replay integrations require serializable values.

Clears the current step’s navigation-work or lifecycle-effect error. It is a no-op when no error is present.

Calls listener after every committed snapshot; read the current one with getSnapshot().

const stop = machine.subscriptions.subscribe(() => render(machine.getSnapshot()));

A navigation publishes twice — once mid-flight, once settled — so a renderer sees the in-flight state (transition.pending, currentStep.async) rather than only the resting one. Core does not de-duplicate by a derived value: skipping unchanged slices belongs to the caller. In React that is useSelector, which owns the comparison anyway to keep render identity stable.

subscriptions.subscribeEvent(event, listener)

Section titled “subscriptions.subscribeEvent(event, listener)”

Subscribes to one of stepEnter, stepLeave, statusChange, contextChange, navigationBlocked, or error.

const stop = machine.subscriptions.subscribeEvent("navigationBlocked", ({ reason, from, to }) => {
console.log({ reason, from, to });
});

Both methods return an idempotent unsubscribe function.

Runs after commit as an awaited source-step side effect. It may be asynchronous and cannot block.

Runs synchronously with { context, handlers }. It must stay pure because snapshot derivation also evaluates it.

Runs post-commit after source onLeave and before destination onEnter. It may be asynchronous and cannot block.

Runs post-commit after onTransition. It may be asynchronous and cannot block.

{
(snapshot, from, to, event, updateContext, raise);
}

event is null unless a graph transition caused the move. raise(event) queues graph work after settle and is a no-op in linear journeys.

Plugin APIs and snapshot extensions are namespaced by plugin name:

machine.plugins.analytics.trackAnalyticsEvent("checkout_opened");
machine.getSnapshot().plugins.persistence;

Irreversibly drops listeners, plugin disposal callbacks, and pending raised events. Later methods are safe no-ops; navigation returns reason: "disposed".