![State Machines in JavaScript with XState, v2](https://static.frontendmasters.com/assets/courses/2021-12-21-xstate-v2/thumb.webp)
Check out a free preview of the full State Machines in JavaScript with XState, v2 course
The "State Modeling 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 live codes the solution to the state modeling exercise.
Transcript from the "State Modeling Solution" Lesson
[00:00:00]
>> Let's go over how we would represent this state machine using either a switch statement or an object in code. So just like we did for the simple promise example, we're gonna create a transition function over here and we're gonna have a state and an event. And remember too that this state is going to look something like status: 'idle' or status: 'loading'.
[00:00:32]
But just to keep with the way that we're doing things in XState, I'd like to have it just as a value. And so I consider this the value of the finite state in the machine. So remember, instead of switching on the event, we're gonna be switching on that finite state.
[00:00:50]
So switch and then we're gonna say state.value. And so now we have three different states that we're going to be considering. We have the loading state, loading, playing, and paused. So case 'loading', case 'playing', and case 'paused'. Default: break. Now, talking about impossible states really real quick, these are really the only possible states that we should have.
[00:01:26]
So if you really want to, you could throw an error to make yourself feel better and say this should be impossible. But, yeah, using XState or other libraries, it prevents this too, because it ensures that only these possible states happen in your application. Okay, so now, we're going to just return the state, just to make sure.
[00:01:54]
So in the loading state, we see that only one event can happen. We could have a loaded event and so we should go into the playing state when that loaded event happens. Instead of using a switch statement, cuz it's a little bit too verbose, we're just going to have an if statement, if (event.type === 'LOADED'), then we're gonna return the state where the value is playing.
[00:02:26]
And so we're gonna be doing that with the other ones as well. So if the event type is pause, then we're going to go to the paused state. If the event is play in the paused state, we're going to be going back to the playing state. And so this is our entire switch statement over here representing all of the events, states, and transitions, the three main building blocks for building this finite state machine.
[00:02:56]
But we also do need an initialState too. So const initialState=, and we're just gonna have value, this is going to be loading. In fact, we could do something that I believe Redux does, where if we don't provide the state or just have this explicitly undefined, just say that this is going to be the initialState.
[00:03:25]
So now, let's attach this to the window, window.transition=transition, and let's play around with it. So we're gonna go back to the media player over here. And let's first try the initialState and say (type: 'PLAY'). Now notice I'm intentionally doing the PLAY event to show you that it should not be handled in the loading state because we're still loading the song.
[00:03:53]
So you see that the value is still loading just like we expect. But now if I have loaded, now it goes to playing, again just as we expect. And so if I take this and put this over here, and now that we're in the playing state, let's say play happens again, of course, it's still going to be playing.
[00:04:16]
And if I have the PAUSE event, it's going to be paused. And so that's how you could do it using a switch statement. But there is another way in that by using the objects notation as well, which I find a little bit simpler and it comes with some other benefits as well.
[00:04:36]
So I'm going to be making a machine over here, providing the initialState of loading and again, we have three states. We have loading, playing, and paused. So in the loading state, on the loaded events, we go to playing. In the playing state, on the PAUSE event, we go to paused.
[00:05:04]
And in the paused state, on the PLAY event, we go to playing. And now we could take that same function that we wrote in our scratchpad for just looking things up on this machine, and I'm just gonna copy it here from the final.js. Just gonna copy this. And I'm going to call this machineTransition.
[00:05:34]
And I'm gonna be using the same technique that we used before and just give this an initialState of value: machine.initial. So now it becomes configurable, and we're going to be looking up a couple things. First, we're going to be looking up that state object based on the value.
[00:05:55]
So for example, if the value is loading, we're grabbing this entire object, and then we're going to be looking up the OnProperty and seeing if there are any transitions. Remember, it might not be defined, so we're gonna put this optional accessor over here. And then we're going to look up if there is a transition on that event.
[00:06:18]
Now, if any of this is undefined, which is shown over here, if we don't have an XState value, we're going to stay in the currentState. This is how state machines work. If events are not handled in the states that we specify, or that we're currently in, then it stays in the same state.
[00:06:36]
So just consider that a feature of state machines. So window.machineTransition=machineTransition. And so, this is going to behave exactly the same way as before. So, let's say we have transition, let's call this machineTransition instead. And playerMachineObject, aha, that's because I'm into this machine. There we go. So now we go to playing, just as expected.
[00:07:10]
And again if we copy this over, we have machineTransition and we have a type of PAUSE, then this is going to go to the paused state. And same thing if we're in the paused state and the PLAY event happens, now we're going to be going to the playing state itself.
[00:07:31]
Now, you could get creative with this, this is just a pure function. And pure functions, as awesome as they are especially if you're in love with functional programming. Not having side effects or way to persist states doesn't really make your application that useful. So thankfully we could use this pure function in a way that we could persist the currentState.
[00:07:55]
And like I said, you could get really creative with this, I'm just going to have a really simple implementation here. So I'm just gonna say let currentState and that's just going to be our initialState of value and machine.initial. And then I'm going to say const, we're gonna call this a service.
[00:08:21]
And so this service is going to allow us to send events to it. So we could send: (event). And so we're going to say the currentState should be the result of the currentState and the event that was passed in. And we'll also consol.log, the currentState, just to make sure it does exactly what we expect it to.
[00:08:51]
So window.service=service. And again, this is a very simple implementation, put this in class, have fun with it, make it subscribable, do whatever you want. But the main point is that now it's something that's live. We're persisting the currentState just in this local variable over here, actually it's a global variable.
[00:09:12]
And we're also imperatively sending events to the service. So, let's try it out. Service.send, so again we're in the initialState right now. So if I send type of loaded, we're gonna get playing. And so again because my state is persisted, if I do PLAY, it's gonna stay on play.
[00:09:37]
If I do PAUSE, now it's going to be paused. So I could send a gibberish event, and because that event is not handled, it's going to stay in exactly the same state. And so that is a solution to the exercise. Now, also, two things I wanna mention. First of all, if you do wanna go to the solution of the exercise and see it, and I do encourage you to do so if you get stuck, you could always take a look at the final.js file.
[00:10:10]
And in the actual application HTML, you could uncomment the main.final.js just to make sure that you see the final result. And you could make sure that what you're doing matches up with that final result.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops