# Hierarchical state nodes

These XState v4 docs are no longer maintained

XState v5 is out now! Read more about XState v5 (opens new window)

🆕 Find more about parent and child states in XState (opens new window) as well as a no-code introduction to parent states (opens new window).

In statecharts, states can be nested within other states. These nested states are called compound states. To learn more, read the compound states section in our introduction to statecharts.

# API

The following example is a traffic light machine with nested states:

const pedestrianStates = {
  initial: 'walk',
  states: {
    walk: {
      on: {
        PED_COUNTDOWN: { target: 'wait' }
      }
    },
    wait: {
      on: {
        PED_COUNTDOWN: { target: 'stop' }
      }
    },
    stop: {},
    blinking: {}
  }
};

const lightMachine = createMachine({
  key: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: { target: 'yellow' }
      }
    },
    yellow: {
      on: {
        TIMER: { target: 'red' }
      }
    },
    red: {
      on: {
        TIMER: { target: 'green' }
      },
      ...pedestrianStates
    }
  },
  on: {
    POWER_OUTAGE: { target: '.red.blinking' },
    POWER_RESTORED: { target: '.red' }
  }
});

The 'green' and 'yellow' states are simple states - they have no child states. In contrast, the 'red' state is a compound state since it is composed of substates (the pedestrianStates).

# Initial states

When a compound state is entered, its initial state is immediately entered as well. In the following traffic light machine example:

  • the 'red' state is entered
  • since 'red' has an initial state of 'walk', the { red: 'walk' } state is ultimately entered.
console.log(lightMachine.transition('yellow', { type: 'TIMER' }).value);
// => {
//   red: 'walk'
// }

# Events

When a simple state does not handle an event, that event is propagated up to its parent state to be handled. In the following traffic light machine example:

  • the { red: 'stop' } state does not handle the 'TIMER' event
  • the 'TIMER' event is sent to the 'red' parent state, which handles the event.
console.log(lightMachine.transition({ red: 'stop' }, { type: 'TIMER' }).value);
// => 'green'

If neither a state nor any of its ancestor (parent) states handle an event, no transition happens. In strict mode (specified in the machine configuration), this will throw an error.

console.log(lightMachine.transition('green', { type: 'UNKNOWN' }).value);
// => 'green'