State Machines in JavaScript with XState, v2

State Machine in Vanilla JavaScript

State Machines in JavaScript with XState, v2

Check out a free preview of the full State Machines in JavaScript with XState, v2 course

The "State Machine in Vanilla JavaScript" 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 creating a finite state machine to handle state switches and correctly handle state with multiple event triggers. How to use an object as a lookup table instead of injecting into a library is also covered in this segment.


Transcript from the "State Machine in Vanilla JavaScript" Lesson

>> So I'm going to go into the scratch pad over here, and we're going to start by creating a very simple state machine. That's doesn't need any libraries, it's just, we're going to be using just a switch statement and we're going to be modeling a simple prompt. So I'm just going to have over here, function, let's just call this transition.

And so this is going to describe transitioning the states or describing what the next state should be given this current state in events. And we're going to be describing like I said, a simple fetch flow, so we have state and event. And so, typically we would start by saying or by having a switch on the events dot type.

So this is assuming that the events looks like type FETCH or it could have other payload like that in the states. It doesn't matter exactly what it looks like but let's assume that the state looks like states, we have data, null, error, null, things like that. All right, so when we start switching on the event type, we might do something like case FETCH.

And then we could say console dot log, starting to fetch data, break and defaults break. Okay, so I'm just gonna say, window dot transition equals transition. And we're going to attach this directly to the window, so we could start playing around with it. And I encourage you to do the same thing if you're playing around with things in the scratchpad as well.

So we could transition again the state doesn't matter right now, but let's say that we have a type of FETCH. Okay, so now it says starting to fetch data, but the immediate issue here is that. The user or even something else in the system can send that fetch events multiple times and it begin to start fetching data repetitively.

And so we want to avoid unwanted states and unwanted actions like that. So that's why, let's just assume that we have a status over here. So let's say that we have status of idle and so we have starting to fetch data, break. It's something that we would typically do, well actually first let's change the state.

So we're going to return status loading and we're going to see if that's and just as a follow through, we're going to return the state. Okay, so now, if we transition status, let's say that we're idle and we send the FETCH events. Okay, so now it says status loading, but what happens if the status is already loading?

It's going to fetch data again and so this is something that you might immediately see if you start clicking a button multiple times. So your first inclination would be probably to have a little bit of defensive logic to solve this problem. So we would say, if state dot status is not loading, Then we're going to start to fetch data.

So now again, if the status is loading and we sent fetch, nothing happens. And so this is the first step to starting to think in terms of states first. So instead of switching on the events type first, what we really want to do is switch some finite state.

So we could use the status in this example and say switch state dot status, and then say case idle. And now we could do our same fetch data over here and so case loading. We could have maybe some other behavior, break and then defaults, break. And then, now what you could do in here and this might look a little bit ugly.

But you could switch on the events dot type if you have multiple events. Or if you're only handling a couple of events per state, what I'd like to do is just put it in an if statement. So I say, if event dot type is fetch, then we're going to start loading the data.

And then we're going to return that status, otherwise, we're just going to return the state. And so now, we're going to see that if we have idle over here. It's going to start to fetch data and it's going to return our next date of loading. And so now, when we load it again, like I showed you over here, it's not going to do anything.

So keep in mind the difference between doing this and taking a state first approach and taking an events first approach. With the state first approach, we don't need to have defensive logic, again this could just be a switch statements. And we don't need to cover up any impossible transitions or states that might arise by doing things events first.

And so the big reason for this is the way we define finite states. Finite states in state machines and state charts, what these represents are behaviors. And so, a behavior is how the application is going to react based on an events. The example I'd like to give is, as humans we're either asleep or awake and those are two different behaviors.

So we could call those states, I'm either in the asleep state or the awake state. And the reason that I would distinguish between those two states is because. I'm going to react differently to events when I'm asleep rather than when I'm awake. And so that should be a good guide for thinking about like how to separate the different reactions to events in your application.

So for example with this fetch logic or separating the idle behavior from the loading behavior. In idle, when the events fetch is sent, then we should start fetching data. In the loading behavior, when the fetch event is sent, nothing should happen. So, that's a good way to distinguish what the different behaviors are.

So, one more thing before I get to the exercise. We could also represent this state first approach, which is actually a finite state machine. We could represent this as an object instead and again, we're not going to be using any libraries. I'm just going to be using an object as a lookup table, so I'm going to call this const machine and I'm going to give it an initial state.

So remember, I'm just using this as a pure lookup table rather than something. That we inject into a library, which we're gonna be doing in the next lesson. So let's say I give the initial state of idle and so now I have a bunch of states, so I have idle and I have loading.

So inside this object, I could specify that on the FETCH events, we should go to the loading states. And we'll just keep it simple for now and keep it like this. By the way, the reason that I'm not just putting fetched directly on the object. Is because we might want to, or in the future, we're going to want to add entry and exit actions and other things.

So this object doesn't just represent transitions, but it could represent other things as well. All right, so how do we use this machine to actually work the same way as this transition machine over here. I'm gonna call this transition2, and let's give it a states and an event.

So, now instead of creating this big switch statements, we could just look it up on the object. And we could say const nextStates equals the machine dot states. And we're going to be grabbing the, let's just grab the state over here. And remember we're putting it on state dot status, the finite state and then on which might or might not exist and the events dot type.

Now, if this doesn't give us a state, like if it's an event that we don't handle. Then we're just going to return the current state and then we could return status, next status, just like that. So window dot transition2 equals transition2 and let's test it out. So I'm also going to be exporting or sharing the machine as well, so window dot machine equals machine.

And so if I console dot log the machine, we're going to see that we have our machine over here. And then transition2, now we could build up this initial states with status machine dot initial. Because we have it over here in a nice convenient property, and then we're going to send the events.

Again we are going to be sending a FETCH event and so what happens is, it takes us to the loading state. So this is going to work exactly the same way as our switch statements. And it's going to default to staying at the same state if there are no transitions to find for that events.

So whichever way you decide to write it without a library, whether you're using an object or a switch statements, it's up to you. The question was, is the transition function a reducer? Yes, the transition function is essentially a reducer. So if you're used to Redux or view X or NGR X or any of those other libraries that make use of these reducers, then it's pretty much the exact same thing.

Some of the differences though, are that while it does return the next date in response to a state in an event. It could also contain other things too which we're going to be seeing in the X state. And it sort of has this specific structure where this state is going to have some sort of field that represents the finite states of the machine.

And of course it could represent other things as well but conceptually you can think of it as a reducer. So again, going over the building blocks of what a finite state machine is. Now that we seen it in code, let's take a look at it visually. So we have the idle state and that was our initial state.

So, we also have a symbol that represents that this is an initial state, now we also have loading. And so you could see these states are represented by boxes. So now if we want to describe how the idle state moves to the loading states. In state machine notation we would just draw an arrow like this and then we would add the events that causes the transition.

So, in this case, we have a fetch event. So now the graphical representation of this logic is exactly the same as we're expressing it in code. Whether it's using the object or using the the switch statements. And so to follow this, you would first look for the initial state in this case it's idle.

And then depending on what events the machine receives, you would follow the arrow. So let's say we're in idle and the fetch event happened. So we're going to follow this arrow from idle to loading because that's where the fetch transition takes us. So now if we get a fetch event in the loading states then nothing is going to happen.

And the reason nothing is going to happen is because there's no outgoing transitions from the loading state. And so this is exactly what the finite state machine really helps with, is preventing impossible states and transitions. If instead we had this arrow just floating randomly and going to the loading state.

Then there are the chance that we could have unintended transitions. But because this is a well defined visual formalism, we know that it's impossible. For a fetch events to do anything when we're in the loading states, just looking at the graph we could see nothing should happen.

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