Skip to content
Version: XState v5

Parent states

States can contain more states, also known as child states. These child states are only active when the parent state is active.

Child states are nested inside their parent states. Parent states are also known as compound states.

In the video player above, the Opened state is a parent state to the PlayingPaused, and Stopped states. These states, their transitions, and their events are nested inside the Opened state.

Jump to learning more about parent and child states in XState

Add a child state

  1. Select the state that will become the parent state.
  2. Use the plus icon button to open the edit menu.
  3. Choose Child state from the menu to add a child state to your state.

If a state already contains child states, you can double-click inside the parent state to create another child state.

Change the parent state of a child state

Using the state  Details panel:

  1. Select the child state you want to reparent.
  2. Open the state Details panel from the right tool menu.
  3. Choose your desired new parent from the Parent dropdown menu.

Using copy and paste from the edit menu

  1. Right-click the child state you want to reparent to open the edit menu.
  2. Select Copy to copy the state.
  3. Right-click the desired new parent to open the edit menu.
  4. Select Paste to paste the child state into the parent state.

Using copy and paste keyboard shortcuts

  1. Select the child state you want to reparent.
  2. Use Command/Ctrl + C to copy the state.
  3. Select the desired new parent state.
  4. Use Command/Ctrl + V to paste the child state into the parent state.

Root state

The state machine itself is a parent state! It’s the root state, and it’s always active.

It's normal to have a state machine that has no other states. This is useful for modeling a simple state machine that only handles events via transitions:

Example of a simple counting machine with increment, decrement, reset, and no states:

import { createMachine } from 'xstate';

const countingMachine = createMachine({
id: 'counting',
initial: 'active',
on: {
increment: {
actions: assign({ count: ({ context }) => context.count + 1 }),
},
decrement: {
actions: assign({ count: ({ context }) => context.count - 1 }),
},
reset: {
actions: assign({ count: 0 }),
},
},
// No child states!
});

Initial state

The initial state of a parent state is the state that is entered when the parent state is entered. Parent states must have an initial states.

You specify the initial state via the initial property of the parent state, which is the key of the initial state in the states object:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
initial: 'question',
states: {
question: {
// ...
},
form: {
// ...
},
thanks: {
// ...
},
},
});
  • Even if the parent state is never directly targeted and its child states are instead targeted, specifying the initial state in the .initial property is required. In this case, the .initial property can be any of the child states.

Transitions on parent states

A transition that targets a parent state will enter the parent state and its initial state. If that initial state is a parent state, then that state's initial state will be entered, and so on.

When an event is received, transitions on the deepest child nodes are checked first to see if any of them are enabled by that event. If no transitions are enabled, then transitions on the parent state are checked. If no transitions on the parent state are enabled, then transitions on the parent's parent state is checked, and so on.

Transitions on a parent state can target child (or descendent) states. This is useful for modeling a transition that should go to a specific child state regardless of which child state is currently active.

Transitions on a child state can target the parent state, though this is not common. A transition from a child state to its parent (or ancestor) state will also enter the parent state's initial state.

Modeling

Coming soon

  • Start with a flat structure; don't create parent states too early
  • If many states have common outgoing transitions, that's a good sign for putting them in a parent state
  • Parent states also represent sub-processes. Very useful when

TypeScript

Coming soon

Cheatsheet

// The machine is the root-level parent state
const machine = createMachine({
// Initial child state of the machine
initial: 'parent',
states: {
parent: {
// Initial child state of the parent state
initial: 'child1',
states: {
child1: {
on: {
// Targeting a sibling
toSibling: {
target: 'child2',
},
},
},
child2: {
initial: 'grandchild1',
states: {
grandchild1: {},
grandchild2: {},
},
},
},
on: {
// Targeting a child
toChild: {
target: '.child1',
},
// Targeting a grandchild
toGrandchild: {
target: '.child2.grandchild2',
},
},
},
},
});