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 there should be a limited number of possible states that the machine can be in. Finite states define how a machine "behaves" when in a state; e.g. a status or a mode. They represent how a machine "behaves" when in a state; e.g. a status or a mode; a machine can behave differently depending what state it is in.
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 = interpret(feedbackMachine).start();
// Finite state
console.log(feedbackActor.getSnapshot().value);
// logs 'prompt'
// Context ("extended state")
console.log(feedbackActor.getSnapshot().context);
// logs { name: '', email: '', feedback: '' }
Coming soon… .getSnapshot()
and show .value
and .context
Finite states in Stately Studio​
- Coming soon… set initial state
- Coming soon… change initial states
- Coming soon… unreachable states
- Coming soon… states with same key
Coming soon… image
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 = interpret(feedbackMachine).start();
console.log(feedbackActor..getSnapshot().hasTag('visible'));
// logs true
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 = interpret(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, interpret } from 'xstate';
const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
'feedback.good': {
target: 'thanks',
},
},
},
thanks: {
/* ... */
},
// ...
},
});
const feedbackActor = interpret(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, interpret } 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, interpret } 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, interpret } 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, interpret } 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, interpret } 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, interpret } 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
TypeScript​
Coming soon
Cheatsheet​
Coming soon