Skip to content
Version: XState v5

State

A state describes the machine’s status or mode, which could be as simple as Paused and Playing. A state machine can only be in one state at a time.

These states are “finite”; the machine can only move through the states you’ve pre-defined.

Jump to learning more about the state object in XState

Using states in Stately Studio

In Stately Studio, the rounded rectangle boxes are states. The ? question mark icon in the machine above indicates an unreachable state. The state is unreachable because it isn’t connected to the initial state by a transition.

Create a state

  1. Select your machine or an existing state.
  2. Create a new state inside your machine or existing state:
    • Use the + State button that appears above the selected state, or:
    • Drag from the handles on the left, right and bottom sides of the selected state, and release to create a connecting transition, event and new state.
    • Double-click inside your machine or an existing state.

State object

The state object represents the current state of a running machine (actor) and contains the following properties:

  • value: the current state value, which is either:
    • a string representing a simple state like 'playing', or:
    • an object representing nested states like { paused: 'buffering' }.
  • context: the current context (extended state.)
  • meta: an object containing state node meta data.
const feedbackMachine = createMachine({
id: 'feedback',
initial: 'question',
context: {
feedback: '',
},
states: {
question: {
meta: {
question: 'How was your experience?',
},
},
},
});

const actor = interpret(feedbackMachine);
actor.start();

console.log(actor.getSnapshot());
// Logs an object containing:
// {
// value: 'question',
// context: {
// feedback: ''
// },
// meta: {
// 'feedback.question': {
// question: 'How was your experience?'
// }
// }
// }

Accessing state snapshots

You can access an actor’s the emitted state (or snapshot) by subscribing to or reading from the actor’s .getSnapshot() method.

const actor = interpret(feedbackMachine);

actor.subscribe((snapshot) => {
console.log(snapshot);
// logs the current snapshot state, e.g.:
// { value: 'question', ... }
// { value: 'thanks', ... }
});

actor.start();

console.log(actor.getSnapshot());
// logs { value: 'question', ... }

State value

A state machine with nested states (or statechart) is a tree-like structure where each node is a state node. The root state node is the top-level state node that represents the entire machine. The root node may have child state nodes, which may have child state nodes, and so on.

The state value is an object that represents all the active state nodes in a machine. For state machines that have state nodes without child state nodes, the state value is a string:

Coming soon… a visual example.

  • state.value === 'question'
  • state.value === 'thanks'
  • state.value === 'closed'

For state machines with parent state nodes, the state value is an object:

Coming soon… a visual example.

  • state.value === { form: 'invalid' } - this represents a state machine with an active child node with key form that has an active child node with key invalid

For state machines with parallel state nodes, the state value contains object(s) with multiple keys for each state node region:

state.value ===
{
monitor: 'on',
mode: 'dark',
};

State machines may also have no state nodes other than the root state node. For these state machines, the state value is null.

State context

State machines can have context, which is an object that represents the extended state of the machine. The context is immutable, and can only be updated by assigning to it in an action. You can read the state.context property to get the current context.

const currentState = feedbackActor.getSnapshot();

console.log(currentState.context);
// logs { feedback: '' }
  • Object is empty {} (default) if context is not specified in the machine config
  • Never mutate this object; should be treated as immutable/read-only

State children

The state.children property represents all currently spawned/invoked actors in the current state. It is an object with keys representing the actor IDs and values representing the ActorRef instances.

  • This is where you access spawned/invoked actors by their ID
  • This is why you should give spawned/invoked actors IDs
  • Stopped actors will not appear here

state.can(eventType)

The state.can(event) method determines whether an event object will cause a state change if sent to the interpreted machine. The method will return true if the state will change due to the event being sent; otherwise the method will return false:

const feedbackMachine = createMachine({
// ...
states: {
form: {
// ...
on: {
'feedback.submit': {
guard: 'isValid',
target: 'thanks',
},
},
},
},
});

const feedbackActor = interpret(feedbackMachine).start();

// ...

const currentState = feedbackActor.getSnapshot();

console.log(currentState.can({ type: 'feedback.submit' }));
// logs `true` if the 'feedback.submit' event will cause a transition, which will occur if:
// - the current state is 'form'
// - the 'isValid' guard evaluates to `true`

A state is considered “changed” if a transition is enabled for the given state and event object.

state.hasTag(tag)

The state.hasTag(tag) method determines whether any state nodes in the current state value have the given tag. This is useful for determining whether a state is a particular state, or whether a state is a member of a particular state group.

const feedbackMachine = createMachine({
// ...
states: {
submitting: {
tags: ['loading'],
// ...
},
},
});

const feedbackActor = interpret(feedbackMachine).start();

const currentState = feedbackActor.getSnapshot();

const showLoadingSpinner = currentState.hasTag('loading');

Prefer using state.hasTag(tag) over state.matches(stateValue), as state.hasTag(tag) is more resilient to changes in the machine.

state.matches(stateValue)

The state.matches(stateValue) method determines whether the current state.value matches the given stateValue. If the current state.value is a "subset" of the provided stateValue, then the method will return true.

// state.value === 'question'
state.matches('question'); // true

// state.value === { form: 'invalid' }
state.matches('form'); // true
state.matches('question'); // false
state.matches({ form: 'invalid' }); // true
state.matches({ form: 'valid' }); // false

state.output

The state.output property represents the output data of a state machine in its final state.

  • state.done will be true

state.meta

The state.meta property represents the metadata of all the state nodes in the state. It is an object with keys that represent the state node IDs, and values that are the metadata of that state node.

state.toStrings

The state.toStrings() method returns an array of strings that represent all of the state value paths. For example, assuming the current state.value is { red: 'stop' }:

console.log(state.value);
// => { red: 'stop' }

console.log(state.toStrings());
// => ['red', 'red.stop']

The state.toStrings() method is useful for representing the current state in string-based environments, such as in CSS classes or data-attributes.

State descriptions

You can add .description to states to describe their purpose and share related notes with your team. In Stately Studio’s editor, these descriptions are rendered in the machine and support markdown including links, images, and lists. Read more about descriptions in Stately Studio.

states: {
"Loading Move Destinations": {
description:
"Load data from the server based on the entity's id and type (project or machine).\nResult includes the entity's current location, and the list or tree of valid destination options to which the user may move that entity.",
invoke: {
src: "loadMoveData",
id: "loadMoveData",
onDone: [
{
target: "Destination Menu",
actions: "setDestinations",
},
],
onError: [
{
target: "Data Loading Error",
},
],
},
},
}

TypeScript

Coming soon

Cheatsheet

Coming soon

Further resources

Persisting state