In the 5th post of the series, we saw how to invoke a state machine in React. Now that we have learned about statecharts to model nested and parallel states, we want to learn how to invoke it and determine its current state.
As seen in the codesandbox for today's lecture, the code to invoke the statechart does not differ from invoking a regular state machine in XState. This is one of many reasons why XState is awesome. By abstracting both state machines and statecharts into the same machine object that is being accepted by the useMachine
hook from the @xstate/react
package, XState provides a really clean API surface to define and invoke machines. As a result, it removes a lot of cognitive overhead whether we are dealing with a regular state machine (Day 2 - 8) or some sophisticated statechart (Day 9).
The API stays the same:
const [state, send] = useMachine(videoChatMachine);
One thing that does change however is the way we assert the current state of our statechart.
Just to recap, our state structure of the videoChatMachine
looked like this:
interface VideoChatStateSchema {
states: {
audio: {
states: {
enabled: {};
disabled: {};
};
};
video: {
states: {
enabled: {};
disabled: {};
};
};
};
}
Due to the fact that video
and audio
are parallel to each other (you can't see this within the state schema as the type
of the machine only needs to be specified in the implementation of the machine), they both must have an initial state and are stateful at all times.
As a consequence, state.matches("audio")
will always return true
. If we want to assert if the audio
is enabled
or disabled
, we have to match against the nested state structure. Luckily, the state.matches
API does not only accept a string value, it's an overloaded function (are you allowed to say that in JavaScript? 😁) that also accepts an object. The object should replicate our exact state structure up until the state node that we want to match against.
state.matches({audio: 'enabled'})
Should your state architecture grow deeper, you can apply the same concept - of creating an object that replicates the state structure - to match deeply nested states.
interface SampleStateSchema {
states: {
grandparentStateNode: {
states: {
parentStateNode: {
states: {
childStateNode: {};
someOtherChildStateNode: {};
};
};
};
};
};
}
// define & invoke machine ...
If you then want to assert that the invoked machine is inside the childStateNode
, you can do so by passing a deeply nested object to the matches
function.
state.matches({ grandparentStateNode: { parentStateNode: 'childStateNode' } });
About this series
Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.
The first couple of days will be spent on the fundamentals before we'll progress to more advanced concepts.
Top comments (2)
If I am not mistaken, dot notation is supported too for matches call right? I find the object too verbose.
state.matches('grandparentStateNode.parentStateNode.childStateNode')
feels somewhat easier to read. Obviously it's not possible to type check correctly, so that's a disadvantage for sure.
Thank you for pointing out. I just recently learned about the dot notation and find it cleaner too.
I believe state.matches does not yet have strict type support. For the time being, it's just a matter of preference which syntax to use.