Modelling API requests with Statecharts

Statecharts are a very useful tool for modelling complex workflows in your application.

XState is a full-featured JavaScript library, for working with statecharts and finite state machines.

Here’s how to model an API request in XState, using an invoked promise. It’s a little intimidating, but I walk through the important part below.

import axios from 'axios'
import { Machine, interpret } from 'xstate'

const getUser = id =>
  axios.get(`/api/users/${id}`).then(response => response.data)

const config = {
  id: 'example',
  initial: 'idle',
  context: { userId: 123 },
  states: {
    idle: {
      on: { GET_USER: 'loading' },
    },
    loading: {
      invoke: {
        id: 'getUser',
        src: (context, event) => getUser(context.userId),
        onDone: {
          target: 'success',
          actions: (context, event) => console.log(event.data),
        },
        onError: {
          target: 'failure',
          actions: (context, event) => console.error(event.data),
        },
      },
    },
    success: { type: 'final' },
    failure: { type: 'final' },
  },
}

const machine = Machine(config)
const service = intepret(machine)

service
  .onTransition(state => console.log(state.value))
  .start()
  .send('GET_USER')

The bit that matters

This is where the real action takes place:

invoke: {
  src: (context, event) => getUser(context.userId)
}

When the statechart enters the loading state, we immediately:

  1. Retrieve the userId from the statechart context object
  2. Call the getUser function, passing it the user ID.

The getUser function returns a promise. XState automatically runs the onDone transition when the promise resolves. If the promise is rejected, XState runs the onError transition. In both cases, we can access the value returned by the promise via the event.data property.

Sign up for my newsletter

A monthly round-up of blog posts, projects, and internet oddments.