β€’

tech

Actors and State Machines will solve State Management

Learn what actors and the actor model are and how to use them for state management. Combine XState and Effect to manage complexity and state in any app, both frontend and backend.


Sandro Maglione

Sandro Maglione

Software

Ever heard about Actors, the Actor model, Message-passing?

It's the next level of doing state management πŸ’πŸΌβ€β™‚οΈ

I got a glimpse into the future of state management with Actors (XState) this week.

Let me share this with you πŸ‘‡


Tech stack

  • XState: since v5 the main building block of XState is an actor. This week I learned why and how πŸ’‘
  • Effect: combine the actor model of XState with Effect (services, layers, error handling) and you will obtain an immense power πŸ”₯

Setup

No limit here. Both XState and Effect are 0 dependencies, plain typescript: they work everywhere πŸ’πŸΌβ€β™‚οΈ

I used Vite with React πŸ‘‡

"dependencies": {
  "@xstate/react": "^4.0.3",
  "effect": "^2.3.2",
  "framer-motion": "^11.0.3",
  "nanoid": "^5.0.5",
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "xstate": "^5.6.2"
}

Get started

This week project is an editor for animated code snippets πŸŽ₯

2 building blocks:

  • A single XState machine to manage the state (inside machine πŸ‘‡)
  • Effect services and layers to organize dependencies
A single "machine" folder contains all the XState code, while Effect is used to organize services and layers ("Highlight" and "Highlighter")
A single "machine" folder contains all the XState code, while Effect is used to organize services and layers ("Highlight" and "Highlighter")

There is more 🀩

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

Implementation

Now, what's the deal with actors? πŸ€”

Each actor has:

  • Encapsulated (private) state
  • Communication by sending and receiving events asynchronously
  • Create (spawn/invoke) new actors

In practice this means:

  • Manage internal state, without the risk of unintended mutations
  • Clear logic using events: nothing changes if no events are sent
  • Coordination and concurrency: each actor responsible for a subset of the system
  • Easy to scale: create/spawn new actors for new requirements

[…] there is no such thing as a single source of truth in any non-trivial application. All applications, even front-end apps, are distributed at some level

Local reasoning solves state management

Since every application is distributed, we need a (reliable) way to manage complexity.

Solution: local reasoning πŸ’‘

Local Reasoning: property of some code wherein the correctness of the code can be inferred locally, without considering prior application state or all possible inputs (Source)

It makes sense: the system is a big-complex-distributed beast, but with local reasoning we only care about a small subset that works independently from the whole app.

Actors for state management

Example: event that needs to fetch some async resources, compute and update the current state 🀯

  • What about errors? What if something goes wrong (it's async after all πŸ’πŸΌβ€β™‚οΈ)?
  • What happens while the async request is processing?
  • Who is responsible to update the state?

Actors solution:

  1. Send an update event to the main actor
  2. The actor spawns another actor responsible to process the request
  3. The sub-actor works independently on the async task
  4. When done, the sub-actor reports the output (fail or success) back to the parent actor
  5. The parent actor gets the result and updates its own state
export const editorMachine = setup({
  actors: {
    // 4️⃣ Sub-actor works independently on the request
    onAddEvent: fromPromise<
      Partial<Context.Context>,
      { params: Events.AddEvent; context: Context.Context }
    >(({ input: { context, params } }) => Actions.onAddEvent(context, params)),
  },
}).createMachine({
  id: "editor-machine",
  context: Context.Context,
  initial: "Idle",
  states: {
    AddingLines: {
      invoke: {
        // 2️⃣ Spawn child actor
        src: "onAddEvent",
        input: ({ context, event }) => {
          if (event.type === "add-event") {
            // 3️⃣ Pass required parameters to the sub-actor
            return { context, params: event };
          }

          throw new Error("Unexpected event type");
        },
        // 5️⃣ Handle error result
        onError: {
            target: ".Idle",
        },
        // 5️⃣ Handle success result
        onDone: {
          target: "Idle",
          // πŸ‘‡ Success: Update parent actor state (`assign`)
          actions: assign(({ event }) => event.output),
        },
      },
    },
    Idle: {
      on: {
        // 1️⃣ Send event to the main actor
        "add-event": {
          target: "AddingLines",
        },
      },
    },
  },
});

The sub-actor (onAddEvent) is independent: this unlocks local reasoning πŸš€

// No need to know about the full app, just focus on your own internal logic (with `Effect`) πŸͺ„
export const onAddEvent = (
  context: Context.Context,
  params: Events.AddEvent
): Promise<Partial<Context.Context>> =>
  Effect.gen(function* (_) {
    /// ...
  }).pipe(
    /// ...
    Effect.runPromise
  );

This scales to any level of complexity: you can spawn even more complex actors, each working independently, with their own state and clear events to communicate

Embrace this new power πŸ”₯


I wrote a complete step-by-step article on how state machines and actors work in XState v5.

Don't miss this, it's the future (and present) of state management πŸ”₯

Takeaways

  • Actors and state machines will solve state management (at all levels of complexity)
  • XState + Effect is the most powerful combination of libraries in Typescript
  • Complexity cannot be avoided, but it can be managed πŸ› οΈ
  • Every app is a distributed system: local reasoning to the rescue 🦸

You can read the full code on the open source repository πŸ‘‡

Open Source Repository

Next week it's the week: the Effect Days are here πŸš€

I will share my full experience at the conference and all behind the scenes πŸ”œ

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