Sustainable xState5 machines
Tags: xstate, webdev, typescript, nextjs
Published: 2023-07-24 13:37:10

### TLDR
If you are curious about the full-working example, it is [here](https://github.com/g-todorov/sustainable-xstate5-machines-example).

### 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](https://stately.ai/blog/announcing-xstate-v5-beta#self-reference) 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:
```javascript
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" }),
  };
}
```

```javascript
/* ... */
assignToggleRef: assign({
  toggleRef: (context, event) => {
    return toggleMachineCreate({ initial: "on" }).spawn();
  },
/* ... */
```

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

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

  return {
    machine,
  };
}
```

```javascript
/* ... */
assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachineCreate({ initial: "on" }).machine);
  },
/* ... */
```

### 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:
```javascript
export function toggleMachineCreate({ initial = "off" }): {
  machine: ReturnType<typeof toggleMachine>;
  spawn: () => ToggleMachineActor;
} {
  const machine = toggleMachine({ initial }).withContext(...);

  return {
    machine,
  };
}
```
now is possible eliminate the need for the `toggleMachineCreate`:

```javascript
/* ... */
assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachine({ initial: "on" }), {input: {...}});
  },
/* ... */
```

### 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:

```javascript
states: {
  initializing: {
    always: [{ guard: "isChecked", target: "on" }, { target: "off" }],
  },
  off: {
    on: {
      TOGGLE: {
        target: "on",
      },
    },
  },
  on: {
    on: {
      TOGGLE: {
        target: "off",
      },
    },
  },
}
```
And using the machine is as simple as:

```javascript
  const [state, send] = useActor(toggleMachine, {
    input: { isInitiallyChecked: true },
  });
```
or 

```javascript
assignToggleRef: assign({
  toggleRef: ({ spawn }) => {
    return spawn(toggleMachine, {
      input: { isInitiallyChecked: true },
    });
  },
}),
```

###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.