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 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.
Let’s start with an example of a traffic light system. In this system, the traffic light can be in one of two modes:
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;
green
, yellow
, and red
.normal
, which is green
.green
substate of the normal
state.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.
+------------------+ +-------------+
| Normal | <---BLINK--| Blinking |
| +--------+-----+ | +-------------+
| | Green -> Yellow -> Red |
+------------------+ | STOP |
+------+
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.
Let’s consider a music player with two independent features:
These two features operate independently, which is a good use case for 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;
musicPlayerMachine
has parallel states.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
.
+-----------------+ +----------------+
| Playback | | Volume |
| +-------+------+ | | +------+------|
| |Stopped|Playing| | |Muted|Unmuted|
| +------+ | |
| |Paused | | |
+---------+-------+ +----------------+
Let’s integrate a state machine with hierarchical and parallel states into a React 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;
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.
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.