January 22, 2019

State Machines: Our First XState Machine

edit

In a previous post, I explained what a state machine is and how to build one from scratch. In this post, we’re going to learn how to make our first state machine using the XState library. First step? You guessed it. Install the library into your project.

npm install --save xstate

Next, we’re going to get the Machine function and the interpret function from the library. These functions are very similar to the ones I made in the other post, so these might seem familiar.

import { Machine } from 'xstate'
import { interpret } from 'xstate/lib/interpreter'

Now, let’s create an example. I’m going to use the same example that I used in an egghead lesson I made to introduce this topic, an elevator. Specifically, a single elevator box.

The box of an elevator can have three possible states: stopped, moving up, or moving down. Let’s start configuring our elevatorMachine with these states.

import { Machine } from 'xstate'
import { interpret } from 'xstate/lib/interpreter'

const elevatorMachine = Machine({
  states: {
    stop: {},
    up: {},
    down: {},
  },
})

Let’s give our machine an initial state of stop next.

import { Machine } from 'xstate'
import { interpret } from 'xstate/lib/interpreter'

const elevatorMachine = Machine({
  initial: 'stop',
  states: {
    stop: {},
    up: {},
    down: {},
  },
})

Now, we need to define the transitions for each state. For each state of our elevator machine, we can transition to the other two possible states. With XState, we define these using a state’s on property, like so:

import { Machine } from 'xstate'
import { interpret } from 'xstate/lib/interpreter'

const elevatorMachine = Machine({
  initial: 'stop'
  states: {
    stop: {
      on: {
        UP: 'up',
        DOWN: 'down'
      }
    },
    up: {
      on: {
        STOP: 'stop',
        DOWN: 'down'
      }
    },
    down: {
      on: {
        STOP: 'stop',
        UP: 'up'
      }
    }
  }
})

Now that we’ve enumerated our states and event transitions, we can use the interpret function to make our machine useful. This function will allow us to define an action to occur for each transition, as well as start the machine itself.

import { Machine } from 'xstate'
import { interpret } from 'xstate/lib/interpreter'

// ...previous code for our machine

const elevatorService = interpret(elevatorMachine)
  .onTransition(state => {
    console.log(state.value)
  })
  .start() // 'stop'

Now we can use the send method on our elevatorService to send events and update the state.

elevatorService.send('UP') // 'up'
elevatorService.send('DOWN') // 'down'
elevatorService.send('STOP') // 'stop'

Now, what happens if we send an event that’s not one of the enumerated possibilities?

elevatorService.send('FOO') // 'stop'

Absolutely nothing! Which is exactly what we want to happen. Only defined events should have an effect on our machine. That being said, you might be the sort of person who wants to throw up a red flag, so to speak, when something like this might occur. In that case, XState machines can be put into strict mode. In strict mode, a machine will throw an error when a non-enumerated event is sent.

const elevatorMachine = Machine({
  strict: true,
  // ...the rest of the machine configuration from before
})

// ...and all the interpreter code from before as well
elevatorService.send('FOO')
// Error: Machine '(machine)' does not accept event 'FOO'

Whoops! XState didn’t like that. An error was thrown indicating an unaccounted event was attempted, but the error message leaves something wanting. Wouldn’t it be nice to be able to identify what machine broke in the message? This is where giving a state machine an id can be really handy.

const elevatorMachine = Machine({
  id: 'elevator-1',
  // ... the rest of the machine configuration from before
})

// ...and all the interpreter code from before as well
elevatorService.send('FOO')
// Error: Machine 'elevator-1' does not accept event 'FOO'

And with that, we’ve finished making our first state machine with XState. I hope that you find this helpful as you get started using the library. I’ll cover all the important parts with more depth soon.

If you’d like to see some of this code in action, you can check out this quick CodeSandbox I made below. Using only plain JavaScript, I added some buttons and a little “elevator” that the state machine controls. Try it out. I recommend hitting the “DOWN” button first, the elevator will go through the roof if you don’t!


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Need help with your software problems?

My team and I are ready to help you. Hire Agathist to build your next great project or to improve one of your existing ones.

Get in touch
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Agathist
Good software by good people.
Visit https://agath.ist to learn more
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.
Logo for Array.reduce()
Array.reduce()
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.