Sviatoslav Oleksiv

Advanced Concepts: Hierarchical and Parallel States

In previous lessons, we covered the basics of XState, including defining state machines, using context, and handling side effects. In this lesson, we will dive into advanced concepts like hierarchical and parallel states. These concepts allow you to manage more complex application logic by organizing states in a way that mirrors the complexity of real-world systems.

You will learn:

Hierarchical (Nested) States

Hierarchical states, also called nested states, allow you to represent a state machine within a state. This is useful when a state can have its own substates that need to be managed separately.

Example: A Traffic Light with Hierarchical States

Let’s start with an example of a traffic light system. In this system, the traffic light can be in one of two modes:

  1. Normal Mode: The light cycles through red, yellow, and green.
  2. Blinking Mode: The light flashes yellow for maintenance purposes.

Code Example: Traffic Light with Hierarchical States

import { createMachine } from 'xstate';

const trafficLightMachine = createMachine({
  id: 'trafficLight',
  initial: 'normal',
  states: {
    normal: {
      initial: 'green',  // Initial substate
      states: {
        green: {
          on: { TIMER: 'yellow' }
        },
        yellow: {
          on: { TIMER: 'red' }
        },
        red: {
          on: { TIMER: 'green' }
        }
      },
      on: {
        BLINK: 'blinking'  // Transition to blinking mode
      }
    },
    blinking: {
      on: {
        STOP: 'normal.green'  // Go back to normal mode, starting at green
      }
    }
  }
});

export default trafficLightMachine;

Explanation:

With this hierarchical setup, the traffic light can either cycle through its normal states or switch to a blinking mode during maintenance. By using nested states, we keep the logic clean and maintainable.

Visual Representation:

+------------------+           +-------------+
|     Normal       | <---BLINK--|  Blinking   |
| +--------+-----+ |            +-------------+
| | Green -> Yellow -> Red |
+------------------+     | STOP |
                          +------+

Parallel States

Parallel states allow you to run multiple state machines concurrently within the same parent state. This is useful when you need to manage independent but related states simultaneously.

Example: Music Player with Parallel States

Let’s consider a music player with two independent features:

  1. Playback: The player can be in one of three states—playing, paused, or stopped.
  2. Volume Control: The volume can be muted or unmuted, regardless of the playback state.

These two features operate independently, which is a good use case for parallel states.

Code Example: Music Player with Parallel States

import { createMachine } from 'xstate';

const musicPlayerMachine = createMachine({
  id: 'musicPlayer',
  type: 'parallel',  // Declares that this machine has parallel states
  states: {
    playback: {
      initial: 'stopped',
      states: {
        stopped: {
          on: { PLAY: 'playing' }
        },
        playing: {
          on: { PAUSE: 'paused', STOP: 'stopped' }
        },
        paused: {
          on: { PLAY: 'playing', STOP: 'stopped' }
        }
      }
    },
    volume: {
      initial: 'unmuted',
      states: {
        unmuted: {
          on: { MUTE: 'muted' }
        },
        muted: {
          on: { UNMUTE: 'unmuted' }
        }
      }
    }
  }
});

export default musicPlayerMachine;

Explanation:

The music player can be in multiple states at the same time. For example, it can be playing and muted simultaneously, or paused and unmuted.

Visual Representation:

+-----------------+               +----------------+
|    Playback     |               |    Volume      |
| +-------+------+ |               | +------+------|
| |Stopped|Playing|                | |Muted|Unmuted|
|         +------+                |                |
|         |Paused |                |                |
+---------+-------+                +----------------+

Integrating Hierarchical and Parallel States in React

Let’s integrate a state machine with hierarchical and parallel states into a React component.

Code Example: Music Player Component

import React from 'react';
import { useMachine } from '@xstate/react';
import musicPlayerMachine from './musicPlayerMachine';

const MusicPlayer = () => {
  const [state, send] = useMachine(musicPlayerMachine);

  return (
    <div>
      <div>
        <h2>Playback State: {state.value.playback}</h2>
        <button onClick={() => send('PLAY')}>Play</button>
        <button onClick={() => send('PAUSE')}>Pause</button>
        <button onClick={() => send('STOP')}>Stop</button>
      </div>
      <div>
        <h2>Volume State: {state.value.volume}</h2>
        <button onClick={() => send('MUTE')}>Mute</button>
        <button onClick={() => send('UNMUTE')}>Unmute</button>
      </div>
    </div>
  );
};

export default MusicPlayer;

Explanation:

In this example, the user can control playback and volume independently using buttons that send events to the state machine. The UI will update based on the current state of each parallel region.

Conclusion

In this lesson, we explored advanced state management techniques using hierarchical and parallel states in XState. Hierarchical states help manage complex, nested state logic by structuring states into substates. Parallel states allow you to run multiple independent state machines simultaneously. These concepts are powerful tools for organizing and managing complex state logic in real-world applications.

In the next lesson, we’ll dive into testing and debugging state machines in XState to ensure they behave predictably.


Next Lesson: Testing and Debugging XState in React