Skip to content
Version: XState v5

Finite states

A finite state is one of the possible states that a state machine can be in at any given time. It’s called “finite” because state machines have a known limited number of possible states. A state represents how a machine “behaves” when in that state; its status or mode.

For example in a feedback form, you can be in a state where you are filling out the form, or a state where the form is being submitted. You cannot be filling out the form and submitting it at the same time; this is an “impossible state.”

State machines always start at an initial state, and may end at a final state. The state machine is always in a finite state.

const feedbackMachine = createMachine({
id: 'feedback',

// Initial state
initial: 'prompt',

// Finite states
states: {
prompt: {
/* ... */
},
form: {
/* ... */
},
thanks: {
/* ... */
},
closed: {
/* ... */
},
},
});

You can combine finite states with context, which make up the overall state of a machine:

const feedbackMachine = createMachine({
id: 'feedback',
context: {
name: '',
email: '',
feedback: '',
},

initial: 'prompt',
states: {
prompt: {
/* ... */
},
},
});

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

// Finite state
console.log(feedbackActor.getSnapshot().value);
// logs 'prompt'

// Context ("extended state")
console.log(feedbackActor.getSnapshot().context);
// logs { name: '', email: '', feedback: '' }

Initial state

The initial state is the state that the machine starts in. It is defined by the initial property on the machine config:

const feedbackMachine = createMachine({
id: 'feedback',

// Initial state
initial: 'prompt',

// Finite states
states: {
prompt: {
/* ... */
},
// ...
},
});

Read more about initial states.

State nodes

In XState, a state node is a finite state “nodes” that comprise the entire statechart tree. State nodes are defined on the states property of other state nodes, including the root machine config (which itself is a state node):

// The machine is the root state node
const feedbackMachine = createMachine({
id: 'feedback',
initial: 'prompt',

// State nodes
states: {
// State node
prompt: {
/* ... */
},
// State node
form: {
/* ... */
},
// State node
thanks: {
/* ... */
},
// State node
closed: {
/* ... */
},
},
});

Tags

State nodes can have tags, which are string terms that help group or categorize the state node. For example, you can signify which state nodes represent states in which data is being loaded by using a “loading” tag, and determine if a state contains those tagged state nodes with state.hasTag(tag):

const feedbackMachine = createMachine({
id: 'feedback',
initial: 'prompt',
states: {
prompt: {
tags: ['visible'],
// ...
},
form: {
tags: ['visible'],
// ...
},
thanks: {
tags: ['visible', 'confetti'],
// ...
},
closed: {
tags: ['hidden'],
},
},
});

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

console.log(feedbackActor.getSnapshot().hasTag('visible'));
// logs true

Read more about tags.

Meta

Meta data is static data that describes relevant properties of a state node. You can specify meta data on the .meta property of any state node. This can be useful for displaying information about a state node in a UI, or for generating documentation.

The state.meta property collects the .meta data from all active state nodes and places them in an object with the state node’s ID as the key and the .meta data as the value:

const feedbackMachine = createMachine({
id: 'feedback',
initial: 'prompt',
meta: {
title: 'Feedback',
},
states: {
prompt: {
meta: {
content: 'How was your experience?',
},
},
form: {
meta: {
content: 'Please fill out the form below.',
},
},
thanks: {
meta: {
content: 'Thank you for your feedback!',
},
},
closed: {},
},
});

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

console.log(feedbackActor.getSnapshot().meta);
// logs the object:
// {
// feedback: {
// title: 'Feedback',
// },
// 'feedback.prompt': {
// content: 'How was your experience?',
// }
// }

Transitions

Transitions are how you move from one finite state to another. They are defined by the on property on a state node:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
'feedback.good': {
target: 'thanks',
},
},
},
thanks: {
/* ... */
},
// ...
},
});

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

console.log(feedbackActor.getSnapshot().value);
// logs 'prompt'

feedbackActor.send({ type: 'feedback.good' });

console.log(feedbackActor.getSnapshot().value);
// logs 'thanks'

Read more about events and transitions.

Targets

A transition’s target property defines where the machine should go when the transition is taken. Normally, it targets a sibling state node:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
'feedback.good': {
// Targets the sibling `thanks` state node
target: 'thanks',
},
},
},
thanks: {
/* ... */
},
// ...
},
});

The target can also target a descendant of a sibling state node:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
'feedback.good': {
// Targets the sibling `thanks.happy` state node
target: 'thanks.happy',
},
},
},
thanks: {
initial: 'normal',
states: {
normal: {},
happy: {},
},
},
// ...
},
});

When the target state node is a descendant of the source state node, the source state node key can be omitted:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
// ...
states: {
closed: {
initial: 'normal',
states: {
normal: {},
keypress: {},
},
},
},
on: {
'feedback.close': {
// Targets the descendant `closed` state node
target: '.closed',
},
'key.escape': {
// Targets the descendant `closed.keypress` state node
target: '.closed.keypress',
},
},
});

When the state node doesn’t change; i.e., the source and target state nodes are the same, the target property can be omitted:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
// ...
states: {
form: {
on: {
'feedback.update': {
// No target defined – stay on the `form` state node
// Equivalent to `target: '.form'` or `target: undefined`
actions: 'updateForm',
},
},
},
},
});

State nodes can also be targeted by their id by prefixing the target with a # followed by the state node's id:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
closed: {
id: 'finished',
},
// ...
},
on: {
'feedback.close': {
target: '#finished',
},
},
});

Identifying state nodes

States can be identified with a unique ID: id: 'myState'. This is useful for targeting a state from any other state, even if they have different parent states:

import { createMachine, createActor } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
// ...
closed: {
id: 'finished',
type: 'final',
},
// ...
},
on: {
'feedback.close': {
// Target the `.closed` state by its ID
target: '#finished',
},
},
});

State IDs do not affect the state.value. In the above example, the state.value would still be closed even though the state node is identified as #finished.

Other state types

In statecharts, there are other types of states:

Modeling states

  • Start simple and shallow. Don’t create multiple finite states until it becomes apparent that the behavior of your logic differs depending on some finite state it can be in.
  • Can model choice pseudostates

Finite states and TypeScript

Coming soon

Finite states cheatsheet

Coming soon