β€’

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