Introduction
When I started to learn XState for the first time, I found the docs somewhat difficult to interpret. Perhaps this was because the docs show a number of concepts at the same time that I was not familiar with as a developer coming from a Redux or React Context background. So I decided to write this article to help others learn XState step-by-step!
What is XState?
XState is a state management library for JavaScript. The idea behind it is a little bit different than Redux or React Context as it is not necessary to use a global store or wrap the whole application in a provider. It is just a simple way to manage the state of your application, and separate the logic from the view so it helps you to write clean and readable code.
Getting started
In this tutorial, we will use XState with 3 React examples that will increase in difficulty.
Introductory level (Counter example)
In this example, we will create a simple counter application with React, and use XState to increment and decrement the counter state.
After you create a new React project using create-react-app
, clean the starter template, and build this simple UI in the App.js file.
Now install XState, and add it using this command:
npm install xstate
Also, we will need @xstate/react
to connect XState with React.
npm install @xstate/react
Now, create a new file, name it counterMachine
, and add the following code:
import { createMachine, assign } from 'xstate';
export const counterMachine = createMachine({
context: {
// Here, we will define the initial state of the machine
},
on: {
// Here we will define the events that will trigger the transitions.
},
});
As you can see, we are using the createMachine
function from XState to create a new machine, and we are using the context
property to define the initial state of the machine. Also, we are using the on
property to define the events that will trigger the transitions.
Now, let's create the context
(the initial state to our state machine).
context: {
count: 0,
}
Let's add our two events to our state machine.
First event is INCREMENT
, and it will increment the counter by one.
on: {
INCREMENT: {
actions: assign({
count: (context) => context.count + 1,
}),
},
}
Second event is DECREMENT
, and it will decrement the counter by one.
Then our state machine is ready. Now let's use it in our React component.
Import the useMachine
hook from XState React.
import { useMachine } from '@xstate/react';
And import our state machine itself.
import counterMachine from './counterMachine';
Then, add this line inside our component:
const [state, send] = useMachine(counterMachine);
So, the useMachine
hook will return an array with the current state, and the send
function. And we can use the send
function to trigger the events.
You can use the state in the JSX like this:
<h1>{state.context.count}</h1>
And now, we can trigger the events:
<button onClick={() => send('INCREMENT')}>increment</button>
<button onClick={() => send('DECREMENT')}>decrement</button>
And here is the result, run npm run dev
:
As you see, it's very simple. Now, let's take a step forward and create an intermediate level example.
Intermediate level (Todo example)
Now we understand how to create a state machine and how to use it in React. Let's create a Todo application to learn how to pass a payload to the state machine.
We will use a template similar to the previous one.
Let's start with our state machine.
First, the context of the machine will look like this:
context: {
todos: [],
}
and the events:
on: {
ADD_TODO: {
actions: assign({
todos: (context, event) => [
...context.todos,
event.todo
],
}),
},
}
And this is how we will pass the payload with the send
function to the state machine from the React component.
send('ADD_TODO', { todo: 'Learn XState' });
Here is the example, run npm run dev
:
Try it and program the delete the todo function yourself!
Advanced level (Traffic light example)
We learned how to create a simple state machine in the first example, and learned how to pass a payload in the second example. Now, let's create a traffic light example, and learn about the states in XState state machine.
The state of the state machine is exactly like the traffic light. When the traffic light is red, the next state should be yellow, then green, then back to red again.
So we will create a simple UI like this with three buttons:
And then our state machine will be like this:
export const trafficLightMachine = createMachine({
initial: 'red',
states: {
red: {
on: {
NEXT: {
target: 'yellow',
},
},
},
yellow: {
on: {
NEXT: {
target: 'green',
},
},
},
green: {
on: {
NEXT: {
target: 'red',
},
},
},
},
});
So, as you can see, we have two new properties in the state machine. initial
and states
. And the initial
property is the initial state of the machine. And the states
property is an object that contains all the states of the machine.
And we are using only one event which is NEXT
and it will trigger the transition to the next state.
So let's move on to the React component, and add the useMachine
hook.
To know what the current state of our state machine is, we will use state.value
.
<button
className="red"
disabled={state.value !== 'red'}
onClick={() => send('NEXT')}
>
Red
</button>
So as you see, for each color in the traffic light, we will have a button. And we will disable the button if the current state is not the same as the color. And we will add the onClick
event to the button.
You can test it here, run npm run dev
: