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" }),
};
}
/* ... */
assignToggleRef: assign({
toggleRef: (context, event) => {
return toggleMachineCreate({ initial: "on" }).spawn();
},
/* ... */
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,
};
}
/* ... */
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:
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
:
/* ... */
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:
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:
const [state, send] = useActor(toggleMachine, {
input: { isInitiallyChecked: true },
});
or
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.
Top comments (0)