DEV Community

Cover image for Sustainable xState5 machines
Georgi Todorov
Georgi Todorov

Posted on

Sustainable xState5 machines

TLDR

If you are curious about the full-working example, it is here.

Background

With the upcoming release of xState5, I've decided that it is time to experiment with the released beta and rethink the way I organize my machines. Rewriting this example with the new version felt like a good starting point.

Use case

Following the case from the previous part, in this one, I just want to focus on the possible improvements that come with xState5. There is a great post from Stately's blog that describes the major changes.

Spawn

With the new unified arguments object and making actors first-class citizens, spawning an actor reference feels more organic.

This is what we had so far:

import { spawn } from "xstate";

export function toggleMachineCreate({ initial = "off" }): {
  machine: ReturnType<typeof toggleMachine>;
  spawn: () => ToggleMachineActor;
} {
  const machine = toggleMachine({ initial });

  return {
    machine,
    spawn: () => spawn(machine, { name: "toggleMachine" }),
  };
}
Enter fullscreen mode Exit fullscreen mode
/* ... */
assignToggleRef: assign({
  toggleRef: (context, event) => {
    return toggleMachineCreate({ initial: "on" }).spawn();
  },
/* ... */
Enter fullscreen mode Exit fullscreen mode

And now we are getting the spawn method directly from the assign action:

export function toggleMachineCreate({ initial = "off" }): {
  machine: ReturnType<typeof toggleMachine>;
  spawn: () => ToggleMachineActor;
} {
  const machine = toggleMachine({ initial });

  return {
    machine,
  };
}
Enter fullscreen mode Exit fullscreen mode
/* ... */
assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachineCreate({ initial: "on" }).machine);
  },
/* ... */
Enter fullscreen mode Exit fullscreen mode

Input

The new input data paradigm is a game-changer for the way I write and use the machines. It comes in the place of the withContext method, which makes the function abstraction (toggleMachineCreate) useless.
We no longer need to create a new machine to pass the entire context but can pass our input data directly when spawning.

Instead of:

export function toggleMachineCreate({ initial = "off" }): {
  machine: ReturnType<typeof toggleMachine>;
  spawn: () => ToggleMachineActor;
} {
  const machine = toggleMachine({ initial }).withContext(...);

  return {
    machine,
  };
}
Enter fullscreen mode Exit fullscreen mode

now is possible eliminate the need for the toggleMachineCreate:

/* ... */
assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachine({ initial: "on" }), {input: {...}});
  },
/* ... */
Enter fullscreen mode Exit fullscreen mode

Function factory

With using the new beta methods, the last place where I need the factory machine function is for setting dynamic initial state, which now feels like unnecessary boilerplate code after the cleanup. After some more reading, it turned out that another approach (and the recommended one) of setting initial state is with an eventless transition:

states: {
  initializing: {
    always: [{ guard: "isChecked", target: "on" }, { target: "off" }],
  },
  off: {
    on: {
      TOGGLE: {
        target: "on",
      },
    },
  },
  on: {
    on: {
      TOGGLE: {
        target: "off",
      },
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

And using the machine is as simple as:

  const [state, send] = useActor(toggleMachine, {
    input: { isInitiallyChecked: true },
  });
Enter fullscreen mode Exit fullscreen mode

or

assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachine, {
      input: { isInitiallyChecked: true },
    });
  },
}),
Enter fullscreen mode Exit fullscreen mode

Conclusion:

From the brief encounter I had with xState5, it seems to provide a far better developer experience than its previous version. There is much more to learn during the process of upgrading my projects, but what I've already encountered looks promising.

Top comments (0)