Check out a free preview of the full State Machines in JavaScript with XState, v2 course:
The "Compound States Solution" Lesson is part of the full, State Machines in JavaScript with XState, v2 course featured in this preview video. Here's what you'd learn in this lesson:

David walks through the solution to the compound states exercise. Student questions regarding the differences between tags and meta and what are some best practices for maintaining deeply nested compound states in large machines are also covered in this segment.

Get Unlimited Access Now

Transcript from the "Compound States Solution" Lesson

[00:00:00]
>> Let us implement compound states into our application. So this pause and playing state now need to be children of the ready state. And so we could add the ready state right over here. And then we could move the paused and playing state right into it. So, if I have a states object in here, we could just copy and paste, if I have everything selected, the pause and playing state right into there.

[00:00:29] So now this is the overall structure. We have a ready state, and the ready state has two child states, paused and playing. And so we need to specify an initial state, too, so initial, so just have it playing, and there we go. That is all it takes to move things into a parent state.

[00:00:50] So now you're going to see that the application will continue to work, hopefully if I don't speak too soon, compound states. And so it says invalid transition definition for state node loading. Child state pause does not exist on machine. So let's debug that. We have loading and it goes to target paused.

[00:01:18] So, I forgot, we have to add this to the ready state instead, at least the error message was clear enough. And so now we're in the ready state, which immediately goes to playing, not paused. And so now we can just click play, paused over here, and yeah. So a couple things to keep in mind, too, is that when we're doing stuff in, for example, rendering based on what the state value is, there is a tendency to sort of tie this directly to the machine structure.

[00:01:58] So, if we want to know whether we're in the playing or paused state, we might have something like, const isPlaying = states.matches('playing'). And so this would have worked prior to the refactor, where we moved everything to a ready state. But now we have to change this to state.matches({ ready: 'playing' }), because this is the new structure.

[00:02:26] And so you might be looking at this and thinking, isn't that a little bit fragile? And the answer is yes, and that's why XState has introduced tags as well. So instead of trying to determine the exact structure of the machine in order to match the state against that, we could introduce tags in states.

[00:02:47] So I could just call this playing. And down here we could instead, say, state.hastag('playing'). Which will return true or false if that tag matches one of the state nodes that are active in the state. And so this is going to work whether we have playing as a parent state, or, sorry, this is gonna work whether we have playing as a top-level state or as a child state of a parent state.

[00:03:23] And so it's pretty refactor-proof, and it's a good way of abstracting your machine implementation even further. So, one question in the chat, what differences do we get from tags over meta? And so, yeah, I'll quickly talk about meta, so each state can actually have meta information as well.

[00:03:46] For example, in playing, I could have meta, and summary, the song is playing. And then when we console.log this state, Now you're going to see in the meta, we have a pretty flat structure where we have the full ID of the song, or sorry, of the state, and we have the metadata that we added to that state.

[00:04:17] So we have this text that says, the song is playing, under the summary property that we provided. So you can use this instead of tags, but meta is really meant not for identifying which states are active, which is what tags are for, but really for adding any extra metadata to those states.

[00:04:39] And this is useful, for example, if you want to generate documentation or have some descriptive parameters for testing, and other things, too. You can even use meta for actually rendering parts of your UI without having to hard code all of it. You could just put it directly in meta.

[00:04:59] There's a lot of use cases for that, but they are conceptually different things. Other than using tags, what are some of the best practices for maintaining deeply nested compound states in large machines? And I would say that the first thing is that you should try not to prematurely add these compound states.

[00:05:22] Try to keep your machine as flat as possible until it makes sense to have these compound states. So you could use tags to sort of, at least form a state-level perspective, flatten the structure of the machine so that you only have to read based on the tags. You could also use meta for that same thing.

[00:05:44] And you could also target using IDs if you need to cross ancestor boundaries. So if this state needs to target this loading state, then you could do that by using that ID that you provide. But in general, if you see that your machine is getting a little too big, then you could, and we'll talk about this later, but you could split it up into different actors.

[00:06:13] In the same way that you would just split a mega function up into smaller functions. And yeah, someone mentioned in the chat, too, that I guess that's where actors and nested state machines come in. Yep, that's absolutely correct.