tech

XState at its Extreme

State management looks easy enough, until it doesn't anymore. That's why you should use XState. And if things are really complex, you will need a full actor system. Let's see how.


Sandro Maglione

Sandro Maglione

Software

How complex can state management get? Well, it's bad 🫠

But there is a solution, called xstate. But some complexity cannot be reduced, only managed.

This is how xstate becomes at its extreme 👇


Simple XState for easier tasks

For simple state, it all fits in one machine.

I directly inline the machine on top of the component that uses it, tightly-coupled and simple 🤝

const machine = setup({ /* ... */ }).createMachine({ /* ... */ });

export default function MyComponent() {
  const [snapshot, send] = useMachine(machine);

  // ...
}

In extremely simple cases, no context or no states are needed (usually one of the two).

Below an example of no states for a stopwatch machine (counting ticks):

export const machine = setup({
  types: {
    events: {} as StopwatchTickEvent,
    context: {} as { seconds: number },
    children: {} as { stopwatchActor: "stopwatchActor" },
  },
  actors: {
    stopwatchActor,
  },
}).createMachine({
  context: { seconds: 0 },
  invoke: { id: "stopwatchActor", src: "stopwatchActor" },
  // No states 💁🏼‍♂️
  on: {
    "stopwatch.tick": {
      actions: assign(({ context }) => ({ seconds: context.seconds + 1 })),
    },
  },
});

A single machine at scale

A single machine can (technically) fit any complex workflow. But.

As you fit more and more in a single machine, the complexity starts to re-emerge again 🙌

This will be your experience:

  • Adding more and more to context
  • More and more states and confusing transitions
  • Having to use complex patterns like parallel states

And another key that it's worth its own callout box:

Maintainability and reusability will degrade 🫠

In practice:

  • More and more bugs as you add/update the machine
  • Duplicated code as you copy-paste only a subset of states/logic

I have been there, and it's tough. Here is an example 👇

A single machine inline with the component, 800 lines of code later. This is how complex logic looks like in a single machine.
A single machine inline with the component, 800 lines of code later. This is how complex logic looks like in a single machine.

Actors to the rescue

Before you start blaming xstate itself, know that there is a solution, it's built-in, and it's how xstate is meant to be used.

It's called an Actor 💡

Every sub-state can be extracted, generalized and spawned as an actor 🪄

This is how it goes:

  • Take a group of states and extract them in a separate machine
  • Remove those states from the parent, and instead invoke the child machine
  • The child machine sends only key events to the parent

The parent machine shrinks, and instead it starts having a bunch of actors attached:

export const machine = setup({
  // ...
  actors: {
    mainPracticeActor: MainPracticeActor.machine,
    restoreSessionActor: RestoreSessionActor.machine,
    situationCountActor: SituationCountActor.machine,
    submitSessionStream: SubmitSessionStream.machine
  },
}).createMachine({ /* ... */ });

We gained back the advantages of maintainable and composable code:

  • Each machine is independent, no shared state (it can be reasoned and refactored in isolation)
  • Machines can be composed by simply attaching actors
  • Events between machines are explicit
  • Each machine becomes smaller and easier to understand (and maintain)

Eventually, you build what's called an Actor System 🏗️

What's more: on the component side nothing changes

// 👇 Parent machine, with a complex actor system inside
const machine = setup({ /* ... */ }).createMachine({ /* ... */ });

export default function MyComponent() {
  // 👇 A single hook inside the component, same as before
  const [snapshot, send] = useMachine(machine);

  // ...
}

And that's the full story of xstate. Thanks.


Once again: there is a certain amount of complexity that cannot be removed.

At that point, (current) AI starts to fail, and you better have a tool powerful enough to make it all work. Welcome xstate.

See you next 👋

Start here.

Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights

Not convinced? Well, let me tell you more about it