Skip to content

Snapshot

machine.getSnapshot() returns the complete immutable read model for one point in time.

const snapshot = machine.getSnapshot();
snapshot.type; // "linear" | "graph"
snapshot.status; // "idle" | "running" | "paused" | "completed" | "terminated"
snapshot.context;
snapshot.currentStep;
snapshot.transition;
snapshot.history;
snapshot.machine;
snapshot.plugins;

currentStep is null before initial entry. Otherwise it contains:

FieldMeaning
idCurrent step id.
metadataStatic definition metadata.
isFirstTimeVisittrue only on the first entry of this step in the run.
asyncLoading, success, and error state for current entry work.

Linear current steps add index, isFirstStep, and isLastStep. Graph current steps add isTerminal.

snapshot.transition = {
pending: false,
phase: null, // "working" | "leaving" | "entering" | null
from: null,
to: null
};
snapshot.history = {
timeline: ["account", "review"],
currentIndex: 1,
visited: { account: true, review: true },
canGoBack: true,
canGoForward: false
};

visited has an entry for every declared step.

snapshot.machine carries one field, outcome — how the run ended, once it has.

It used to carry six more: isLoading and one boolean per status. Every one of them restated something the snapshot already said, so they are gone. Read snapshot.status directly for the lifecycle ("idle" | "running" | "paused" | "completed" | "terminated"), and snapshot.transition.pending for “is a move in flight” — transition also tells you the phase and the from/to of that move, which a bare boolean could not. snapshot.currentStep.async is the separate, step-level question: whether the current entry’s own work settled or failed.

snapshot.machine.outcome = null; // or { type: "completed" | "terminated", payload }

Completion and termination set snapshot.machine.outcome; only restart() clears it back to null.

if (snapshot.type === "linear") {
snapshot.steps.stepOrder;
snapshot.steps.totalSteps;
snapshot.steps.visitedStepCount;
snapshot.currentStep?.isLastStep;
}
if (snapshot.type === "graph") {
snapshot.declaredEvents;
snapshot.availableEvents;
snapshot.availableSteps;
snapshot.outgoingTransitions;
snapshot.steps.totalSteps;
snapshot.steps.visitedStepCount;
snapshot.currentStep?.isTerminal;
}

declaredEvents includes every event declared from the current step. availableEvents and availableSteps include only candidates whose guard currently passes. outgoingTransitions explains both projections with each candidate’s target, priority, guard result, enabled state, and whether first-enabled event dispatch would select it. A terminal step has no declared outgoing transitions, regardless of guard results.

Introspection shows the resting-state answer: guards are evaluated against the live context. A work send’s candidates are evaluated during the send against the context its commit staged instead, so a candidate that reports guard: "failed" here can still win the route once the work has run and staged the fact it checks.

Do not mutate a snapshot or its context. Use machine.context.update() and read the next snapshot. Read the slice a consumer needs inside the listener:

machine.subscriptions.subscribe(() => renderAsyncState(machine.getSnapshot().currentStep?.async));