Join the AI Workshop to learn more about AI and how it can be applied to web development. Next cohort February 1st, 2026
The AI-first Web Development BOOTCAMP cohort starts February 24th, 2026. 10 weeks of intensive training and hands-on projects.
XState
XState is a popular JavaScript library for working with finite state machines.
Finite state machines are an interesting way to tackle complex state and state changes and keep your code bugs-free as much as possible.
Just as we model a software projects using various tools to help us design it before building it, finite state machines help us solve state transitions.
Computer programs are all about transitioning from one state to another after an input. Things can get out of control if you’re not paying close attention, and XState is a very helpful tool to help us navigate the state complexity as it grows.
Installation
Install XState using npm:
npm install xstate
Then import it in your program:
import { Machine, interpret } from 'xstate'
In the browser you can also import it from a CDN:
<script src="https://unpkg.com/xstate@4/dist/xstate.js"></script>
This will make a global XState variable on the window object.
Creating a state machine
Use the Machine factory function to create a state machine:
const machine = Machine({
id: 'trafficlights',
initial: 'green',
states: {
green: {},
yellow: {},
red: {}
}
})
Here we defined 3 states: green, yellow, and red.
Defining transitions
To transition from one state to another, we send a message to the machine, and it will know what to do based on the configuration we set:
const machine = Machine({
id: 'trafficlights',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
}
}
}
})
When we’re in the green state and we get a TIMER event, we switch to yellow, and so on.
Triggering transitions
You can get the initial state:
machine.initialState.value //'green'
And switch to a new state using the transition() method:
const currentState = machine.initialState.value
const newState = machine.transition(currentState, 'TIMER')
console.log(newState.value)
Using services
With transition() you always have to keep track of the current state. To avoid this, create a service using interpret():
const toggleService = interpret(machine).start()
Now you can use send() to retrieve the new state:
const toggleService = interpret(machine).start()
toggleService.send('TOGGLE')
Store the return value to get the new state:
const newState = toggleService.send('TOGGLE')
console.log(newState.value)
Multiple transitions from one state
Given a state, you can know what will trigger a state change using its nextEvents property.
Here’s a more complex example with multiple transitions:
const machine = Machine({
id: 'roomlights',
initial: 'nolights',
states: {
nolights: {
on: {
p1: 'l1',
p2: 'l1'
}
},
l1: {
on: {
p1: 'l2',
p2: 'l3'
}
},
l2: {
on: {
p1: 'nolights',
p2: 'nolights'
}
},
l3: {
on: {
p1: 'nolights',
p2: 'nolights'
}
},
}
})
Use it:
const toggleService = interpret(machine).start();
toggleService.send('p1').value //'l1'
toggleService.send('p1').value //'l2'
toggleService.send('p1').value //'nolights'
Adding actions
To do something when we switch to a new state, use actions:
const machine = Machine({
id: 'roomlights',
initial: 'nolights',
states: {
nolights: {
on: {
p1: {
target: 'l1',
actions: 'turnOnL1'
},
p2: {
target: 'l1',
actions: 'turnOnL1'
}
}
},
l1: {
on: {
p1: {
target: 'l2',
actions: 'turnOnL2'
},
p2: {
target: 'l3',
actions: 'turnOnL3'
}
}
},
l2: {
on: {
p1: {
target: 'nolights',
actions: 'turnOffAll'
},
p2: {
target: 'nolights',
actions: 'turnOffAll'
}
}
},
l3: {
on: {
p1: {
target: 'nolights',
actions: 'turnOffAll'
},
p2: {
target: 'nolights',
actions: 'turnOffAll'
}
}
},
}
}, {
actions: {
turnOnL1: (context, event) => {
console.log('turnOnL1')
},
turnOnL2: (context, event) => {
console.log('turnOnL2')
},
turnOnL3: (context, event) => {
console.log('turnOnL3')
},
turnOffAll: (context, event) => {
console.log('turnOffAll')
}
}
})
Each state transition can specify a target (the new state) and actions to run.
You can run multiple actions by passing an array of strings.
You can also define actions inline:
{
on: {
p1: {
target: 'l1',
actions: (context, event) => {
console.log('turnOnL1')
}
}
}
}
This is just scratching the surface of XState. Check the XState Docs for more advanced usage.
Lessons in this unit:
| 0: | Introduction |
| 1: | jQuery |
| 2: | Axios |
| 3: | Moment.js |
| 4: | SWR |
| 5: | ▶︎ XState |
| 6: | PeerJS |