Check out a free preview of the full State Machines in JavaScript with XState, v2 course:
The "Guarded Transitions 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 guarded transitions exercise. A student's question regarding how guards are visualized in the state chart is also covered in this segment.

Get Unlimited Access Now

Transcript from the "Guarded Transitions Solution" Lesson

[00:00:00]
>> In this current exercise, we had to do a few things. And I'm gonna start with the simplest thing first over here, which is making sure the volume is within range. Of course, you could add a condition over here and apply this in line. So, the condition function takes two arguments, the context and the events.

[00:00:20] But we want to make sure that this event level, and we're only focused on the event level but we wanna make sure that that's between 0 and 10. So you can do something like return event.level is greater than 0 and event.level is less than or equal to 10.

[00:00:45] And so now, when we run this, Guards, we see over here that the volume is currently 5. So if we send a volume level of, let's say 6, and they have it conveniently over there ready, then we see that the volume changed to 6, which is great. So now if we send something like 600, That is going to keep it at 6.

[00:01:20] Now one question might be, okay, why do we have to have a guard here where we could just constrain the volume in assignVolume? And you definitely can do that. Here's the difference, though. If you allow this transition to be taken, it's going to basically tell the state machine, this state is changing.

[00:01:42] But if the volume isn't changing at all, then nothing really is changing, so you're sort of lying about the state changing. So, it's just a more concrete way of us describing that, no, nothing is changing. We're not changing the state, we're not running any actions because the volume is not within range.

[00:02:02] And with things like states.can, which is that useful method that tells you if a state is able to accept an event, that is going to be false if the volume is out of range. Because it is going to check this condition and tell you nothing's gonna happen if you send that event.

[00:02:19] Whereas if we had this in assignVolume, then it's gonna say yes, we do accept that event. But nothing will happen, but there's no way of you knowing that nothing will happen. So the second part that I want to go over is using an always or an eventless transition to automatically go somewhere when some condition is true.

[00:02:44] And so we specify that here using always. And one trick to prevent infinite loops, because always functions, or sorry, always transitions are always gonna be taken, is that you want to get in the habit of adding a condition first. So, add the condition that the context.elapsed is greater than or equal to context.duration.

[00:03:10] And so, when that happens, we want to go to the paused state. So now when we run this, and let's say that right now it is currently playing and we have a minute 40. And so, let's say that we have something that changes the elapsed time. And that thing is gonna be audio.time, which is this assignTime here.

[00:03:36] So, I'm going to send type AUDIO.TIME and I'm gonna give it a current time, which is what we have over here. That current time is gonna be something really, really high, like 1000, just trigger this. And so, yeah, you have to add service.send. And so now you see that it immediately went to the paused state because it elapsed or the elapsed time was greater than the duration.

[00:04:07] And so, yeah, that's just a useful way of combining both transitions and guards. And using that within always is a really useful pattern because this, again, doesn't have to be dependent on any specific events. So now if we go to main.final, you could take a look at all of these implemented.

[00:04:36] We have this which is always going to the paused state when the elapsed value is greater than the duration value. And over here we also have the guards for LIKE.TOGGLE. So it's going to check this one first and say, if the like status is liked, and we're using the raise action here again, we raised the unlike event which is going to cause us to unlike the song.

[00:05:04] And if the like status is unliked, then we raise the like event, so we're just going back and forth. Now, some of you observant ones might look at this and be like, isn't this sort of a state machine on its own? And the answer is yes, and so this is also an exercise to the reader.

[00:05:21] You could also implement this as a separate parallel state once we get to that in a couple of lessons. So, the final behavior over here should be, I'll just show it to you right here. So yeah, this is the desired behavior, if you're able to implement it, where it goes between liked and unliked.

[00:05:44] And so also we have the guards preventing the volume from getting too loud and also skipping ahead, or yeah, going to the paused state when the elapsed time is greater than the current duration. One of the questions asked was, when we apply the guards, so cond, how does that look in the actual state chart?

[00:06:07] So we could visualize that here or here. So let me just make an example over here, so const machine = createMachine. All right, so let's say that we have a loading state. And on some event, we only want to do something if the condition is true, so loaded.

[00:06:39] So we could say cond: 'dataReceived', sorry, 'dataValid', and target: 'loaded'. So this is how it's going to look and this is how it's represented in a state chart. We have the event here, some event, and we have the guard here that's represented using these brackets. And so this is typical for state chart notation.

[00:07:05] So whenever you see those brackets, just realize that this event is a guarded transition or is part of a guarded transition.