Check out a free preview of the full State Modeling in React with XState course

The "Guarded Transitions" Lesson is part of the full, State Modeling in React with XState course featured in this preview video. Here's what you'd learn in this lesson:

David explains how guarded transitions prevent state transitions if a condition is not met. The condition is a function which returns a boolean indicating if the transition should run.


Transcript from the "Guarded Transitions" Lesson

>> So the next lesson is going to be on guarded transitions. Guarded transitions are basically a way of saying you cannot transition if this condition is not met. So it is essentially a guard for your transition. So let's say that over here, our alarm example. We don't want this candle to exceed five.

So we don't want the user to keep pressing on and off. Obviously, this is a contrived example, but this just goes to demonstrate how guarded transitions work. So let's go to the scratch index. All right, so currently, we did before we're incrementing the counts every single time we toggle from pending and let's say we don't want this to go past five.

We could do what's called making a guarded transition. So we're essentially saying this toggle transitions shouldn't happen if the count is greater than five. So the way we do that is by adding a condition over here. So it's cond or C-O-N-D over here and this has been the source of a lot of confusion, by the way, in version five.

This is gonna be renamed to guard in. There will be a deprecation warning in the future. But for now, it's called cond. And so this cond takes exactly the same two arguments as our actions do, which is context in event. And so we could say only allowed this transition to happen if context.count is less than or equal to 5.

So, gotta return that. So basically, if the count is 5, it's going to yeah, so we should do less than five just so that we don't allow it to get past 5. So let's try this. Click here, 1, 2, 3, 4, 5. Keeps going let's reload that. Okay, so after reloading 1, 2, 3, 4, 5.

You see that it no longer goes past 5 and that's because that transition it's like that transition doesn't even exist anymore, because of this conditional. And so this is what's called a guarded transition. Now if we wanted to go to another state just as a follow through, then we could do that as well.

And to demonstrate that, I'm gonna go ahead and actually put the state here as well. So also showing you another trick. The state value can be represented as strings. And because you could have nested in parallel states too, it's an array of strings and we'll talk about that in a bit.

But I'm just gonna put state.toStrings, which is an array of strings and we'll join that with a space. So the reason for doing this is because I want to see what the current state is. And then let's say that I want to add another state which is I don't know, rejected.

And let's say that when I toggle, if this transition isn't taken. So it's always gonna check this first transition. I'm gonna add a second transition here that says, go to reject it. So in other words, this is saying, when toggle happens, go to pending and do the increment action only if this condition is true.

So only if context.count is less than 5. Otherwise, go to the rejected state. So now, let's see what happens. Click, OK, four, five. And the when we try to click again, it's gonna go to rejected in which nothing can happen. So that's the guarded transition. Are there any questions on that?

>> Is it wise to have your business logic there? Like I'm picturing like a wiziwig where you're moving in like an address and then you can't go further, because it's an invalid address like is it?
>> I guess my question is more like how much do we bloat the state machine versus just doing it elsewhere?

>> That's a good question. So the question was about how much business logic should we put inside the actual state machine? And so the answer is pretty much the same answer as one of the previous lessons, which was about parameterizing. What we could do is we could take this condition and we could just make an abstract form of the business logic.

Let's say we have a condition called too much. And so I could say const too much equals that saying guard. So basically, the context counts is less than 5, then we have too much. Now we do have to specify that in the second argument over here, which is an object that takes all of these things.

And so over here, that's called guards. This is also another reason why in version five, it's gonna be changed to guard instead of cond, C-O-N-D. And so we could say too much is well, it's the same thing as that tooMuch, guard over here. So now when we reload, we're gonna see that we have the exact same logic it goes to rejected and you could configure that too.

So you could make that per component or per whatever uses that machine. So to answer your question, yes, you should not put too much business logic in the state machine, especially if it's something that needs to change over time or change based on the situation rather.
>> Just a side note on that, isn't the recommendation basically that your machine like definition is completely serializable basically, right?

In which case, you would basically always want to parameterize your various actions and conditions and stuff like that down into the second argument.
>> Okay, so the question was should like is the ultimate goal to make your machine completely serializable as a best practice? I would say yes, definitely.

And the reason why is because number one, it's good for being able to, even if you're not visualizing your machine just taking a look at it and being able to understand it in terms of business logic instead of in terms of the actual implementation details. For example, I could read this like a sentence, go to the pending target.

And do the increment action if there is a, I guess I should call this not too much, [LAUGH] not too much. If the user didn't click this button toMuch, basically. And so that becomes really understandable. Another reason too is because in the future not only visualization tools, but tools such as XD Python which was mentioned earlier ad also being able to convert to and from STX and ML.

They're going to rely on just trying to not have as many implementation details as possible, so that your machines could be sharable, reusable in different languages. So yeah, I definitely think it's important to at first when you're prototyping your machine and just making sure the logic works. It's absolutely fine to do things in line and it's fine to keep it that way too.

But if you do want to make it more concise and more understandable and usable in other contexts, then yeah, it's definitely a worthwhile goal to serialize your entire machine or make it entirely serializable.
>> Rather have a follow up. So like one of my first cracks at a more complex machine with ex state was a kind of a complicated authentication system on our platform.

And I found that a lot of states and lots of actions, and can conditions, and various things, and my deficit machine definition just being really big. And I started to have like PTSD from like Webpack config a little bit. And I'm like man, maybe I need to break this out and do some better like object composition and stuff like that.

Do you kind of like recommend that when these machines get too big or I've just saying I've kind of struggled to find the balance of like where do I start making this more digestible? Once a machine definition gets pretty big, hundreds of lines, basically.
>> Yeah, so the question was about, basically what do you do when your machine is too big?

Are there any patterns for making it more concise or just organizing it better? And [COUGH] the short answer is yes there is and that's something that we're going to get into in the later lessons. But essentially if you have a machine that's too big, you want to start applying separation of concerns are actually just like a segregated responsibility principle.

One of those principles if not both of them, but basically take a look at your machine. Look what logic can be separated into its own entity. And instead of having that all in one big machine, you could have a machine that invokes other machines. Basically, makes other machines responsible for doing those tests in communicating back with the parent machine.

And so that will be the contents of the last exercise. So we'll talk about that definitely at the end of the day.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now