Let’s Learn State Machines
Watch on YouTube (CodeTV)
What is this talk about?
What are state machines? How are they helpful? In this episode, David Khourshid ( walks us through refactoring a React component to use xstate, which helps us clear up some bugs and add more predictability. (From the video description.)
“We did use state machines within the live share the live share extension.”
“When we're in the closed state when we have an open event and that could you know, come from anywhere, we go to the open state.”
From the opening of the conversation.
Key takeaways
- We did use state machines within the live share the live share extension. 4:00
- When we're in the closed state when we have an open event and that could you know, come from anywhere, we go to the open state. 19:00
- Number one with state machines and SCXML, which is the standard that XState is based on, it's send plus send is a lot easier to type. 23:00
- If the hide widget value is false, then we want to focus on the widget title. 30:00
- You know, you'll see that if you click on states the state tab at the right you'll see that now we're in the open state and we have the focus title action. 35:00
- What's good about this is instead of having like just a separate state for set rating, now you could use the state machine to apply just logic to you know, to this. 46:00
Quoted from the transcript. Conversations include other speakers.
Full transcript
From YouTube auto-captions. Technical terms were normalized; some errors remain. 9,782 words.
0:00 Introduction
All right. Hey everybody and welcome to another episode of learn with Jason. Today we've got David Khourshid from Microsoft and he is going to be teaching us about XState. David, I'm super excited to have you on the show because I've heard about state machines for it feels like at least a couple is it a couple years now? Since you did that React Rally talk? Yeah. Yeah, it's been I think that was what, 2017? I'm actually I'm wearing a React Rally shirt right now. So [laughter] excellent. Yeah, so yeah, I'm super excited because I heard about that talk. I remember looking at some like state machine source code. I didn't have time to learn it and then I have never since had a chance to actually figure out how these things work. So it's just been like a persistent state of feeling like I'm missing out on a huge tool.
So I'm hoping to correct that today. At least you're thinking in states. [laughter] yeah, that's I mean that's the hope, right? But so yeah, we are we were going to look at la last week or a couple weeks ago Marcy Sutton and I put together this feedback widget. Let's see. Where is it? GitHub.com/jlangsdorf/ Gatsby feedback widget. I hope I'm getting all of this right. Yeah. Okay, so we put together this feedback widget that is let me find I have a I have a link to it somewhere. Yeah. And for reference, I got this feedback widget literally this morning, basically. [laughter] yeah, so that's the premise of the show, right? Is is that we have no idea what's going on. We're going to make it up on the spot. So we built this widget that allows you to rate your experience. And so, you can you can go in. It's, keyboard navigable.
2:00 We did use state machines within the live share the live…
You you choose how you're feeling. You can add an optional comment, and then you submit it, and that is the entirety doesn't work. [laughter] yeah, this button is broken, so we're going to have to fix that. You have to use the escape key now. Right. But so, basically the the source of this is, two files. And they're a little they get a little long because we got it working. We didn't refactor. But it's kind of set up now with like some pretty gnarly state. Where everything is just kind of like out in the open, and everything is managed, very manually. And so, this seemed to me, like, from what what we were talking about before, this seemed like an excellent candidate for being converted to a state machine because there are specific states that we can be in, specific states we should never be in, and clear actions that point between them.
And if I'm understanding state machines correctly, that's exactly what this is for. Right. Yeah, and so, to get to the root of the problem, you look at this, and let's say that you're probably a new developer on your team, such as me, right? And let's say that I asked, what's what's the logic for this? Like, how's this supposed to work? Like, what does activated mean? What happens, when you know, you go into different states? And so, right now, the logic for this, like, based on my cursory glance, it's, it's sort of all over the place. No no offense. No, [laughter] it it for sure is. like, cuz you can see down here, too, like, we're doing all this very kind of odd stuff. We're doing conditional checks. Like, I'm sure that there are ways that we're getting this wrong right now. Right. Yeah. So, what we could do is yeah, let's just think through the states and if you want to live share.
Yeah, let's do it. I will does this one do the live share? No. What's my Oh, I got to use the other I have two profiles and this one doesn't have it set up. So, let me use this one. Give me just a second. I'm always excited when people use live share. I was previously on the live share team at Microsoft. Oh, cool. And we did use state machines within the live share the live share extension. Just because some of the logic got complicated between like whether you're in the state of sharing a session or if you're joining a session or if you're logging in, logging out, etc. Yeah, that totally makes sense. So, all right, we've got the feedback widget open. I'm going to live share it. Let me send you this link once I get it. Got it. Okay, and I'll send it to you through the not that button.
Through the chat. Here. And I'll move this off screen because while I do enjoy that everybody watches live, I've learned better than to trust y'all since you started messing with my data that one [laughter] time. That's a good call. But yeah, so else adding in here. So. Perfect. All right, so you are in and we will be looking at the source components feedback widget folder. That's where all the that's where everything happens in this thing. And we get to the actual components on about line 89. Mhm. And so, this is running live right now, right? It's not running live, but I can get it running live. All right, yeah. Let's do it. Let me and so what's cool about live share, too, is that you could you could share your local server and tunnel it over to me. Oh, for real? So I can yeah. Okay, I've definitely never done that. [laughter] yeah, yeah, yeah.
It's a really cool feature of live share. All right, so I've got what do I need to run it through the VS code terminal for that to work? No, no, not necessarily. Just all right, we've got Oh, I've got node problems. [laughter] so I use NVM, and I'm pretty sure that I configured it incorrectly because when I move between terminals, half the time my node modules are like, "Hey, you used the wrong version of node to compile this." So someday I'll fix that, but until then I can just do a quick reinstall and everything seems to fix itself. Mhm. Okay, so we've got ourselves a let me check out master, actually. I don't think we changed anything, but yeah, we didn't change anything, but let's yeah, a couple things. Let's do yarn install. files pop in. And now we should have a running feedback widget, which for now I think will be on just my browser. Yeah, so what you could do in live share is you could just command shift P and share the server, but not really necessary.
It's just, you know. Or share. Yep. Share server. See that one. So I just do 8,000? 8,000, yep. Oh my goodness. Yeah. Wow. Okay, so that I mean I feel like that made the whole episode worth it. [laughter] yeah, yeah. So, so now I have the server URL copied to the clipboard, which I'm not going to share just because other people are going to well, actually no, no. It's it's on my localhost, too. But, I see that you're running server in my computer, so it's pretty cool. I mean, others can't see that, but just take my word for it. Whatever Jason could see, I could see right now. That is that is very slick that just works. I thought you were going to make me install like ngrok or something, and I was like, "I don't know if we should do that." [laughter] no, no, much easier than that.
Okay. All right, so let's get started. So, first of all, I realize that a lot of viewers might not be too familiar with what a state machine is. Right. So, oh, let's see. Getting some warnings about it being out of sync. Oh. Let me do you want me to share it again? No, no, no. We're we're good. Got you. Okay. I think it might be because I did a git pull while we were sharing. [clears throat] yeah, probably. So, if you go to the XState GitHub repo page, just want to show something real quick. So, David Khourshid Khourshid oops. XState. [laughter] yeah.
9:00 I experienced this problem when trying to focus inputs that if…
So, there's a reason why the primary example that I have in the read me is just a simple toggle. And that's because a lot of our stateful problems start with this, where we essentially have two states. It's either on or off. And then, eventually, as our apps get more complex, we realize, "wait, we actually have more than two states, like more than this in this case, inactive and active." And so, I think that this could be a good place to start, just taking a look at the feedback app as the widget is either open or closed. Yeah, but I do have one question. So, what does this activated mean? Yeah, so walking through what the code does, I believe I set up here. Yeah, so the problem was is that on mount the widget was auto focusing because we're using refs for keyboard focus. All right. And so by not setting the activated until the component actually mounts, that prevented the auto focus on this effect handler. So, that might just be me being bad at React.
Okay. [laughter] that's a yeah, that's a weird React thing because you actually have two states when React re-renders, you have the okay, here's the state of you know, the state of the world, and then once things are rendered, then that's like a new state. So, I experienced this problem when trying to focus inputs that if you try to focus it in a useEffect before it was rendered, it I mean, the component doesn't exist yet, so it doesn't know what to focus. So, the way I solved that, I mean, you solved that with an activated, but I just used a set timeout. But, that's that's beside the point. Okay. So, yeah, let's make a state machine. So, in short, a state machine is well, the it's five parts. So, first we have a finite number of states, and so just in this case, we have you know, open and closed. Okay. And so, we could just model this at there's also a finite number of events.
So, the user could submit the form, the user could open the form, the user could close the form. Yeah. And there's transitions, basically saying how do states get from one state to the other state based on what events just occurred. Yeah, so we have a couple things here. We've got the feedback widget and then we have a confirmation. I should make this like an actual this is going to get so sloppy and I apologize in advance. So [laughter] I so we have the actual feedback widget which is going to have the like open and closed and then we have the like success screen which also has an open and a closed.
12:00 Submit, close, add a comment, choose a rating
Mhm. And then we like the actions that you can take are to choose like you can choose a thing Woah, what is happening with my keyboard? Here we go. I guess I don't even know what you would call actions. Is you can like select a rating. You can Oh no. Oops. It's all right. It's okay. Sorry, I messed up. Sorry, keep going. Yeah, I can we can select a rating. We can add a comment. We can submit. We can close. I think that's it if I look at this. Yeah. Yeah, submit, close, add a comment, choose a rating. And then once we submit we get well, I think I have that I was going to install XState. Got a little bit ahead of you. And so that's not working right now, but what will happen is if I submit this, it shows us this thank you for your feedback at which point we need to be able to close this.
So, really the only thing that then happens is we have events for the success, which would be to like dismiss or close or whatever we want to call that. Exactly. Yeah. Okay. So, let's add two packages actually. So, we're going to add XState, and then there's a package called @XState/React, and that just gives you a hook.
14:00 For people who aren't familiar with sharp…
A useMachine hook so that you could easily just use a machine within your React component. Okay, so I'm going to add both of these. And let's see, we're putting the computer under strain here cuz it's trying to stream and run npm. So, let's get it running again. Do I need to resave that or reshare that port with you? No. No, it's going to still work. Uh-oh. Why? Come on, sharp. Knock it off. [laughter] for Christ's sake. Okay, so a fun thing about sharp is that it is a pain in my ass. So, we're going to remove node modules and just reinstall. So, for people who aren't familiar with sharp, sharp is a it's a like a not JavaScript binary that we have to install. It's kind of like node sass if you've ever used it. And it's kind of finicky. Like if it gets out of sync with anything, it just breaks.
And so, whenever that happens, you just delete node modules and try again. And nine times out of 10 that'll fix it. So, that's what we're doing right now. So, we'll wait for that to rebuild and reinstall, and let's pop into the Oh, cool. There's a question here that says you can put programmatic focus in useLayoutEffect, which I have actually never used before. So, maybe that would be a way to work around the that all right, so useLayoutEffect. That's you know, I had a hunch that would work. Does that mean that it's an effect that happens after layout? Because that would make the most sense. According to Malkythos, it says it guarantees that it's been painted before it runs. And I haven't actually read the React docs, so I have no reason to doubt. That actually makes sense. Okay. Of course, the React docs scare you away from using useLayoutEffect, which is not helpful when it comes to things where you actually need to use layout like for focusing, because that's the reason I use that time out is because any other use blank effects, I just get scared of.
So. Yeah, thanks React docs. Thinking that the React docs are great. There's just yeah. [laughter] yeah, they have a lot feedback. Yeah, you know, we're doing a whole feedback thing. All right, so what I'm going to do is I'm going to just disable this because I don't want to I don't want to deal with posting a bunch of stuff. And what we'll do instead is we'll just console log the payload to make sure that if we if we need to verify that it's working, we can. And that way when I run this and let's see, I can do this. I'll just submit it, and we get our mutation because I just put a GraphQL payload directly into a an Axios post, because I didn't want to set up a whole GraphQL library for one thing.
17:00 Same thing if we're in the open state…
Cuz I have to include this in another it like it's going on the gatsbyjs.org site. Right. Yeah. But that being said, we are now ready to rock. We've got a list of our states. Yeah. Uh-huh. And we've got XState installed. Yep. Cool. So I'm going to go to the top here and let's start by making a really simple machine. And so this is basically going to be our simplest hello world example of using XState. So I guess first of all, I have to import machine from XState and you could click the pin at the top right in order to follow what I'm doing. The pin at the top. Oh, cool. More more buttons that I didn't know. [laughter] yeah. And so this machine is going to basically define our state machine. It's just a function. You could just think of it or it's an it's an object or an instance of a machine with lots of functions.
So it's I call it stateless, but I got ripped apart on Hacker news for calling it a stateless state machine. No idea why. But I'll I'll show you what I mean. So let's make a const feedback machine. And so it's just a factory function. Give it an idea feedback and that's just for debugging visualization purposes. It's just good to identify what your machine is actually. And so if you see me on line 91 line 91? Yeah. Ah, there we go. There I am. So we're going to give it an initial state of we said that at first it's closed, right? And so right now it could be in two states just for now, open and closed. And so this is going to be our simplest example. And so when we're in the closed state when we have an open event and that could you know, come from anywhere, we go to the open state.
And so same thing if we're in the open state, and we have a close event, then we go to the close state. I guess we should call this opened. Just to make that Oh, yeah. Yeah. unambiguous. Yes. So now, before we do anything, looking at this and trying to visualize the logic in our head is all fine, you know, we could do it with pencil and paper, but I'm going to send you a link. Okay. statecharts.GitHub.io if you click that link, that links you to a visualizer, where we could copy that const feedback machine code, and just paste it right in there. Ooh, cool. All right, let me do that. Let's so you could just remove all that. Got it. And then there you go. And so this visualizes your logic in a pretty clear way, and so you could see like the events are going to do exactly you know, what you say they do.
So this is a this is a prime example of a finite state machine. We have a finite number of states, open and closed, finite number of events, open or close, and we have the transitions that go between the states. Open or closed. Yeah. So what's really cool about this too, and like what I see as being extremely powerful is like you're showing where we start, which is this little dot here. Mhm. And then you show actions, and we can see that like we're in the open state, so we can't open it again, and it just shows us that. And but we can close it, and then once we close it, now we can't close it again, we can only open it. And I realize that this is extraordinarily obvious in this like small example, but yeah. I feel like this is like this is the thing that I keep seeing and I'm like, "Oh my god, I can see why this is such a powerful model." Because you can like you can visualize the logic of your app.
And there was something else that you mentioned I almost in passing that I thought was incredible when we were first talking about this, which was that you had said that using these state machines, there's actually the potential to automate your testing. Yes. Yes, there is. And so I actually have an article that I'm working on coming out for that. And funny enough, the example in the article is a feedback form with a couple more states, but yeah. I- if you look at your feedback form, you'll see that, you know, there's many different ways that a user could go through the feedback form. You know, they could click poor, fine, great. They could type a comment. They might not type a comment. They could press submit. They could press close at any time. There's two different ways they could close. They could press the escape key. They could press the close button.
All sorts of different ways that a user might go through those. And writing integration and end-to-end tests for each of those is pretty tedious, especially as your feedback form or any other component in your app gets more complex over time. Mhm. And especially if the logic changes, then guess what? You have to change all those tests. And so that's why there's a thing called model-based testing, which really automates all that. Yeah. But let's let's save the testing for yeah, we'll we'll sit tight and wait for the article. Yeah, yeah, exactly. So just you know, just a preview of that. So let's actually use this feedback machine in our app. So I'm going to go back up here and I'm going to import useMachine at aptly named from XState React.
23:00 We could send an event and let's send the open event
Okay. And so this is just a hook. And so it's going to look I think there was a typo in that. Let me Oh. Fun. [snorts] [laughter] all right. So this useMachine hook let's put over here. It's going to look a lot like your useReducer hook. So I'm just going to call it current. Send equals useMachine. And we're going to use this feedback machine. Okay. And send is like is like a dispatch in a reducer parlance? Right. But number one with state machines and SCXML, which is the standard that XState is based on, it's send plus send is a lot easier to type. And Mhm. I don't know why we call it dispatch. Like it's too many letters. [laughter] send is easier. You're you're sending an event, right? So let's console.log current.value so we could see what the value is every time we send an event to this machine. Now right now when you reload your app, you're going to see that the value is yeah, I got that bug earlier, too.
Is closed, which is obvious. The initial state of our machine is closed. So that totally makes sense. But let's actually add an action. And so I'm guessing the handle open handler. Yes. So right now we're doing set activated true, which I was a little confused about. But we could send an event and let's send the open event. And then on handle close, let's send the close event. So very simple. All we're doing is sending events. So we're not changing states. We're not trying to do any logic, we're just our event handlers are very minimal. I want to call them stupid, but I think minimal's a nicer term where all it does is send events. Right. And maybe some you know, some housekeeping like event.preventDefault. And there it goes. It's it's doing the thing. Yeah. So, you can see that we do get our states and so, that works pretty well.
Mhm. And so, now we could we could actually use that value in I'm guessing widget wrapper. So, we have this on line 244 we have hidden equals hide widget. Got it. Yeah. So, instead of that we could do current.dot you could do this. You could do current.dot value equals closed. And this is going to work just fine. And if we save that, we'll see that works. Yep. Yeah. But because with XState we have statecharts, and statecharts are state machines, but you could have nested states, you could have parallel states. So, you know, this might be a state like you know, closed foo something else bar. And then it becomes really hard to match against that. You have to do all sort of JavaScript gymnastics with that. Mhm. So, instead, you could do current.dot matches closed. And so, this is going to match, you know, whether the string is like closed or if it's closed something or, you know, whatever. But if closed was false, it would not match?
Right. If it was open, then it would not match. Oh, because the state wouldn't have closed at all. Like you don't you don't keep like a boolean value. Yeah. Ah, okay. That makes a lot of sense. All right. And then we can just go back out here and cycle it again, and it's doing what we expected. Mhm. Yeah, so totally works. And so we could we could almost remove that the well oh, also almost forgot the handle escape key. Yes. So if we take a look at that, you know, we have this handle close event. So instead of that, we could just send close. And so it's going to be the exact same thing. Okay, so hitting the escape key. And it does what we expect. Mhm. I'm not focused. There we go. Yeah, that's that is I like that. That feels very easy. [laughter] yeah, so this simplifies the logic. And so you see like the whole open close logic, Mhm. all of it moved to our state machine.
28:00 When the widget becomes visible…
And so it's self-contained, you know, over here in this feedback machine. Right now, you know, of course the logic is pretty simple. Uh-huh. But it could definitely get more complex. One area of complexity that I saw is this what happens when you're in the open state? Something focuses, right? Yes. You you currently it focuses on the well, it should focus on the header up here. It looks like I there we go. It focuses on the header. And that's so that screen readers know something is opened, and it'll read that header to him. Right. So, this is a prime example of again, me as a new developer coming onto this team, not that I'm leaving Microsoft, but just you know, taking a look at this project with fresh eyes for the first time, wanting to know the business logic of what's supposed to happen. Mhm. So, I I know that clicking the button is supposed to open the widget, but as far as when the widget is opened, we want the title to focus.
Mhm. That logic is not as clear because we sort of have to dig down. Right? And we sort of have to see, okay, what's happening here? I see that we have a you know, useEffects that's sort of doing stuff. Actually, where is it? [laughter] it's it's all the way down on 213. Oh, right, right, so it is the you know, this useEffect stuff. So, reading this, it's like if hide widget equals false, then focus the widget title. So, if we're trying to translate this into plain English, if the widget is not hidden, then focus the widget title. So, does that mean that the widget title should always be focused? Well, it so, [clears throat] right. So, yeah, this is kind of the it's when any of these values changes, then we know that something is up. And if the hide widget value is false, then we want to focus on the widget title.
So, basically, when the widget becomes visible, focus on the title. that's the key. So, you said when the widget becomes visible, translating that, we're saying when the widget gets into a state of visible or opened, right? And so, in with statecharts, there's a very clear way of representing that. So, if we go back up to our statechart over here, so online 94, 94? The pin is struggling with me right now. Okay, so we're we're back up at the feedback machine. Right, so we have an on entry the these are called actions. So, these differ from Redux actions. Redux actions are actually events. Okay. Or we're sending events. Actions are things that happen, like effects. So, well, just for clarity, we'll call this an effect. So, this on entry, we want to focus title. And notice that I'm giving it a string. I'm not actually putting the implementation in here because this is just a it's basically a glorified pure function.
Okay. And so, if we go back down to the feedback widget and instead well, we'll log the value, too, but let's also log currents.dot actions. And Oh, here. Got you. There we go. So, we're logging the value. We're also logging the actions. Okay. And so, let's pop this open. Mhm. So, close Oh, here we go. Got you. there's no actions, but when we're opened, we have we have an action over here. There's no implementation, which is why exec is undefined, but we know that we have to execute this effect, this focus title effect. Okay. So, we could do that using you know what? Do you let's take a chance and let's do this useLayoutEffect. Okay. Maybe that'll solve our problems. But what we could do is we could [clears throat] let's see. currents.actions.forEach. For each action, we could, you know, switch on the action.type. And so, for example, if it's a what do we call it?
Focus title. Yes. So, so if we focus title then I guess we need this which is title and all this stuff up here. [laughter] and just so we have reference to it. So if our action is to focus the title then we could say widget title.current.focus, right? Mhm. And then I'll like prettier. Yeah, prettier will help. [laughter] Yep. Thanks, prettier. I love prettier. Okay, and of course always declare your dependencies. In this case our dependency is current and so current refers to the current state. So obviously when the current state changes then you know we want to execute those actions. And so with these actions are actions don't persist, you know, they're per state. Okay. So I'm going to just drop out the title focus from the useEffect that we have now. Yeah. I'm also going to drop out this actually we don't need to care because that's that is what it is. So let's let's jump in here and let's see Oh, useLayoutEffect is not defined.
Let's pull it in. Mhm. Good call. Use layout. That's the one I want. I also broke useState somehow so let's Okay, good. All right. Let's try that again and now I click focusing title. Yeah. And did it do it? Let me I don't know. All right. Okay, but it didn't focus the title. That's yeah, that's weird. So maybe useLayoutEffect isn't what we want. So my hack for this currently until you know, I'm sure someone will tell us a better way to do this is just to wrap that in a set time out so that it gives us time to render or we could even do request animation frame. Actually, that's probably better. Okay. Again, if there's a better way to do this, I'm all ears, but Okay, and then should we just do a useEffect then? Yeah, this is probably just useEffect. All right, let's give that a shot and see what happens. There you go. Here we go. So, yeah, I guess that works just feels hacky. But the point of it is that our effects become declarative. And so, we could we could put the implementation details later.
Mhm. Which is good. And so, if we were to copy paste our machine to the visualizer again, which we don't have to do, but we'll see that this focus title action is now visible in the machine and so, if you were to hand it off to someone cuz I feel like that is a good thing to yeah, yeah, yeah. So, you know, you'll see that if you click on states the state tab at the right you'll see that now we're in the open state and we have the focus title action. And so, it's it's a declarative representation of what's the next state and what are the next actions to execute. Very cool. Yeah. Okay, and then so, how would we handle say the so, here's where I think things get interesting. So, there are a couple things that I thought would be worth doing because one thing that I've noticed is that when we get in here I click on this. If I click on it again we actually probably don't want that to happen, right?
Because you shouldn't be able to open again when the widget's already open. And so I don't know if like this would be one of those things where I this is kind of a contrived example, but like I think it would you said you should make impossible states impossible. Right. So so you're saying you don't want the title to refocus again, right? Well, we actually just like when once we've clicked the open button, we probably don't want it to continue trying to open. Because it's already right. Exactly. And so right now try just clicking that button many times. Doesn't doesn't seem like anything's happening, so that's good. Because no state is changing. It did it twice. So once, twice, and then it doesn't do it a third time. Ah, okay. Okay. So yeah, so let's see. So that's focusing title opened. So basically you're saying that you don't want side effects to you know, happen more than once.
And when you send an event, whether you like it or not, whether the state changes or not, React's going to say, "Okay, it's time to re-render." Mhm. And that's just because you know, the object references could be different or something like that where React's, you know, diffing says like, "well, it might be changed." You know, we're not going to do a deep diff comparison and figure it out because that's expensive, but we're just going to assume the worst and say so it's not actually trying anything. It's just listing this out. Well, so for useEffects, what we could do is we could [clears throat] there's this thing called if current or sorry, if not current.change. So if the state didn't change, just return. And so this is an XState thing where and this is actually pretty cool. So instead of doing deep object comparisons, XState determines if the state changed based on well, when it's transitioning, it knows that if it stays in the same like open state then it didn't change.
And also if there are no actions or side effects, then again, it didn't change. So, this will probably solve that problem. Ah. Well, I wonder is it because we're only focused the title once. So yeah, so it's I think it's this is actually something different where we are getting this and then when we click it again it's running the check, but it's not like performing any actions. So it's maybe just because we're logging like without any logic. Yeah. Yeah, so again, React's going to be pretty overzealous about re-rendering, you know, just because it's not sure whether things have actually changed or not. Okay. So that's what that current state changed is. It's a it's an optimization that you could use if the state has really changed. Sure. So then another question would be like with this, we're currently using useState to manage the value of these. Do we want that in the state machine or in this particular example like with a form, do you still just want kind of like a local state for form inputs?
So that's up to you. With with XState which, you know, pretty much acts like a glorified useReducer, you could definitely put that all in there.
40:00 The rating by default is two
You could refactor it like slice of state per slice of state. Or you could just like go wholesale, just useReducer for everything. Or well, use XState for everything. And so, yeah, I guess we could let's see. I guess it's that set rating. Mhm. And so, handle change set rating. All right, so, this gets into another fun topic with XState where a lot of people and this was early on like after I presented that React Rally. A lot of people said that they want to represent their state as of course finite states like open, loading, closed, submitted, you know, failure, all those what I call discrete states, but they also have what's called like non-discrete or continuous states that they want to represent, too. So, for example, if they fill the feedback form, there's an infinite number of possibilities like what someone could put in that feedback form. Right. So, that introduces the idea of context or in David Harel who created statecharts, his original formalism, he calls it extended states.
So, state that goes beyond the finite states. So, we go to our feedback machine on line 33 again. Let's just have a rating. Is there any rating selected by default? Yeah, the rating by default is two. Okay. I think. Optimistic. Let's see. I think we it's like one, two, or three are the ratings. Yeah, so, it's we choose right in the middle by default. Right. All right, so, so, we have this rating of two. And so, we want only in the open state, right? Mhm. We want let's say let's call this action rate. Okay. So, when the user performs a rate action, they want to stay in the open state, of course. But they and so we're not going to assign the target. So that this target this is shorthand, so this closes the target is a shorthand for a target closed. But over here we're just going to emit that because we're staying in the same state.
Uh-huh. And so instead we're going to we're going to perform an action. And so I'm I'm going to import assign from XState and actions. So this is going to feel very much like set state or useState. So we're going to assign rating and we get a function with two arguments, the contexts and the events. But we're just going to use the events in this case. And so the events is going to be or let's just do e.value. And just make it simple. Yeah, so we don't need to use that context. Perfect. And so for people who aren't familiar with this, the underscore is just like a linting shorthand to keep it from complaining that we didn't use that variable. Mhm. So if we go to handle change handle change. on yeah, line 175. Okay. Then I'll I'll keep this for now, you know, just to illustrate. But we could, of course, again send the events.
And so it's going to be type rate and the value's going to be events.target.value. Got you. And then I'm going to log on line 143 currents.context. Okay. So this is our finite state.
44:00 We've got a rating of two
This is our extended state. And this is our side effects. Our actions. Okay. So, let's save that. Let's jump out here. And we've got a rating of two. So, let me jump in and I'm going to change that rating. Rating is now three. Rating is now one. But I guess it should I you have to like explicitly cast it because of the way that I guess because of the way React is pulling the value of things. Because we do set it as a as a number, but yeah, so we can we can go change that actually real quick. Let's do number. Oops. Oh, I'm lazy. I just put a plus sign in front. Oh, yeah, that's actually that's actually I mean that is kind of a good like we can just drop that in here and that'll turn it into a number. So let's do that. There we go. And we've got numbers.
And so yeah, and you know, just in terms of refactoring we go to the line 172, we see where that set rating is and this rating. So, we could even come to sound and we could just say const rating equals currents.context. Well, rating. You could even you could even do this. If we want to get fancy. But this really pisses people off who are not familiar with that syntax, but yeah, and I think [laughter] what we'll actually be able to do because the ultimately the way that we're going to run this is down on 197. This is where we use that state. And so, we would be able to almost like just de-structure it down here. At least I think so. Let me make sure we're not using it anywhere else. Yeah, so now this we use it in a few other places. is yeah, nowhere. And so what's good about this is instead of having like just a separate state for set rating, now you could use the state machine to apply just logic to you know, to this.
So, for example, let's say that you don't want to allow them to change the rating for some reason. I don't know. The there's basically instead of directly manipulating state, you send an event that says, "I would like to change the rating." And based on the logic here, you know, you would change the rating. So, over here, we make it absolutely impossible to change the rating if we're in any other state besides open or closed. Now, of course, in the UI, that's I guess that's impossible anyway just because those rating elements are hidden. But, if I if I were to bet you $100 like say, "can you guarantee me that it's impossible for the user to make a rating without it being open?" Then, you might take that bet, but with more complex applications, that might not be the case. For sure. [laughter] because as the logic stands before the state machine, a rating could happen in any state.
And the only guarantee or the only like safeguard you have against ratings happening in any state like closed or submitting is that they're not visible in the UI. Which is [clears throat] a very a very shaky assumption. Yeah. So. Well, cool. So, I mean, this is like this is super handy. And we'll be able to go in here and refactor to also include whoops. Let's go to do add the comment. And then for things like let's see, what else? Oh, so the other thing that we would want to handle is this the transition state between the feedback widgets when it gets submitted then opens the success screen. And yeah, so let's do that. Yeah, that would be a good one, I think. Mhm. So, yeah, so if we go back in our state machine, I added the success state. Mhm. And so now we're thinking through what events causes open to transition to success?
Probably submit. Submit, right? And so right now we're assuming that it just is instantaneous. So, that there's no loading. So, all right. So, and so this is actually a good use case for now that we have three states, this widget wrapper Oh, wait. We're we're good anyway because current match like it's only hidden if current match is closed. So, it's not going to matter. But let's go ahead and let's see, where's our success screen? That is yeah. You're down on let's see, what line is it? Down on line 331. Yeah. Sorry, I don't know why the pin isn't working, but [laughter] so here's another interesting thing that I see in a lot of apps. So, we have two widget wrappers side by side. Mhm. We have the success one and then we have the question one. So, if I asked you, can both of these be showing at the same time?
The answer's probably going to be no, of course not. Like, why would we show success and but then if I ask you, how can you guarantee that? Or how do you know? There's a lot of juggling going on. Like, over here it's is submitted equals false and then over here well, it was something [clears throat] else. It was hide widget or something. Yeah. Yeah, so my question would be like, what if hide widget was false and is submitted is false at the same time? And then you might tell me like, oh well, that doesn't happen. But then I ask you like, can you guarantee that doesn't happen? We actively ran into that while we were live coding this. So, it right. Yeah. That definitely hit us. Yeah. So, let's let's do I mean, just just for now, yeah, we could do this. We could do currents.matches success. Mhm. Because we could be in three states now. We could either be in open, success, or closed.
And so, if it matches closed, then it doesn't match success, obviously. Right. So. And so, let's see, how do we submit? So, we have the submit button. And so, I'm guessing like handle submit function in here. Right, there's that handle submit function. Which does a few things. So, let's see, that logs the payload. So, let's send a submit. In fact, like if we were actually sending data, we would just send type submit and just like the payload. Yeah. And honestly, I would like to move to doing that. So, let's let's go ahead and leave that in and we probably won't have time to fully refactor this, but I'll finish it up later. Yeah, yeah. So, right now, let's go back to the app and see if it works. Okay. We are in the app. And let's see, where Hm. Oh, wait, sorry. This widget wrapper it's hidden if it sorry. Oh, yeah, that's right.
Yeah, so it's that hidden logic. I so, so usually for conditionally displaying certain panels, I would do something like a switch statement. Or not yeah. Or or ternary or something like that. That was so, that's what we should have done. You know, we were we were trying to do this in I think we built this in about like 2 and 1/2 hours. So, it was it was very much like let's see how far we can get. Okay. So, we've got this. We can rate our experience. We're doing great. And let's submit it. And now we've got the success did show up, but we didn't lose the open state. Or something. Right. And so, this is where yeah, this is a great bug to see. Like as weird as that sounds. So, let's real quick refactor this into not a switch statement just because I really wish that JavaScript had those do statements.
But instead let's do this. So, basically if where are you at right now? falsy. So, I'm on line 288. 288, got it. And so, yeah. So, this hidden stuff we're just going to assume that you know, if it's if it if it exists, it's visible. If it doesn't exist, it's hidden. So, this doesn't really matter. we can drop that together then. So, if current matches open, then show this. Okay. Otherwise if current matches success, show this. And of course, we don't need this hidden anymore. And hopefully that doesn't break anything. Otherwise, let's just show null because or well, no, we want to show this button. Well, the button should still be visible. We we always want to show that button. All right. Yeah, so this logic should be fine. Prettier is going to do its thing. So yeah, just to explain to everyone what I did, I refactored this to a ternary statement.
Oops. Cannot focus. No. Oh, fun. [laughter] Okay, so let's look at what went wrong here. We have current matches open. Mhm. And the problem would appear to be that it is trying to focus Oh, oh, oh. It should be opened. Ah, yes. [laughter] we ran to that before. So, now things should work just fine. Got it. Opened. We press submit. it. And we are in the right state. If we fix this button, that would work, but I'll use escape instead. Uh-oh. Our escape button now works. We don't have a dismiss action. All right, so this close button yeah, right now this close button does nothing. Oh, okay. So, the logic this would this would be an example of faulty logic is that on close Mhm. close, like if we go back to the state machine online 101 yes. that and this is sort of a feature slash it's not really a bug, it's just a lack of specification, but let's say that someone says, we want the user to be able to close the feedback machine no matter which form they're on or which state they're on.
Mhm. So, we could have a we we could basically just handle this on the entire machine. So, on line 117 I'm just going to add that close. And so, the way that nested states work is if a nested state doesn't handle an event, this is just sort of like event propagation. If this doesn't handle an event, then it goes up to the parent and it asks the parent, "can you handle the events?" So, it's going to look here first. And if it doesn't find anything, then it'll look here. And then if it doesn't find anything, it's broken. Then it doesn't do anything. Got you. So, now we should see that it indeed works if you go back to the app. And we have Oh, did you already add the handler? I'm about to. Okay, so we are just adding an on click handler that will send close. And save that. Let's get out here and try it again.
So, we're going to submit and dismiss. All right. So, let's do that one more time. Let's use the escape key this time. And it does exactly what we want. So, that is fantastic. Yep. And then you can visualize it's all you're going to see that it's a little bit more complex now, but it's good because, you know, anyone could just look at the visualizer and be able to see the complex logic that we're trying to represent. Okay, so let's update. We're going to get here. And so, the close action can come from anywhere. Yeah. like that's always available. We can always call it and it would just like do nothing if it's already closed. Mhm. If we open, then we're able to rate, which is really that's okay. So, we can see what's going on here. And then the rate that's showing us the X data sign is how we would update the and you could actually send the custom events bottom right.
So, if you put like three there. In the well, wouldn't I have to call it like rate? Would I call it rate? Oh, no, no. Oh, yeah, yeah, sorry. So, type rate and then value three. Then you're going to see that the context does get updated and it stays on the open state. Cool. Okay. And then if I submit it and I try to submit my custom value let's change it. Nothing happens because we're not in that state. Right. Yeah, we it's pretty powerful stuff. And then you could close from well, and then we close from out here. Okay. Very cool. Awesome. So, I feel like we've we've covered quite a bit here.
58:00 Bold could be on or off, italic could be on or…
Are there any other kind of edge cases or features that we should look at in like the let's see the Oh, we've got a couple questions actually. So, light Sam it looks like joined a little bit late. We are building a feedback widget. So, the feedback widget was built. We're refactoring it to use XState. And then there is another question. Could we re could we use React Automata's state tags to handle when the widget wrappers should be visible or not? So, React automata uses XState version 3. I don't think it was updated to the last version yet, but honestly, when React hooks came out it made things so much easier. So, it's easy enough to just pop in a hook like a useMachine hook and then use that state just like you would use a reducer like useReducer. Okay. So, yeah, so we would do it kind of here like something yeah. And then one more question or I guess a request from Ismet Berry, parallel state, please.
I don't [laughter] even know what that means, so I hope you do. I do, but that's like sort of an advanced or not advanced, it's just a deeper topic on its own. And thankfully in our in our feedback widget, we don't really have parallel states, but could you maybe just give a quick overview of what that actually means? Yeah, sure. So, let's imagine that we are making an example which it of we're we're showing like a bold, italic, or underline. Like you could pick between any of those three, right? And so, each one of those buttons exists in its own parallel states. So, bold could be on or off, italic could be on or off, underline could be on or off. And instead of instead of creating like eight different states where it's like bold italic or bolded bold underline, italic underline, etc. We could just represent those as just their own orthogonal regions or parallel states.
And so, in that case, the state value is going to look something like I'll just put it over here. It's going to look like bold active, underline inactive italics active. And so, that's why I like this object notation because it represents that we're in each of these three states at the same time. And each state has its own state. And so, how would you use the current.matches with this because it would always match bold? Right, but you could you would do something like current.matches bold active. And so, that would that would be true for that. Got you. Perfect. Well, cool. All right. If anybody wants to ask questions, now's the time because we are we're nearing the end.
1:01:00 If you click fetch over here fetch. there's a timeout and…
David, is there anything else that you want to kind of show off or dig into here? Well, I just released XState version 4.4 which includes a lot more features such as dynamic times. In fact, if reload the visualizer because it doesn't really you know, save your stuff. I have one example over there that highlights a few more features. So, if you click fetch over here fetch. there's a timeout and you see that timeout where it goes to rejected. So, reset the machine and then click resolve real quick. Well. So, you see that timeout is automatically canceled and now we're in the fulfilled state. Yeah. And we're in a nested state, too. So, we're in fulfilled first and you could just imagine that thing is green. And so, the code for that is pretty straightforward. It's after timeout rejected and you define timeout at the bottom. So, we have timeout and then at the bottom we just like these are all implementation details, which means that the above machine, like everything above, like, you know, in the first argument, all of this stuff can be converted to JSON.
And [clears throat] this business logic or application logic can be reused whether you're using Vue, Angular, React, Backbone, I don't know. No framework or even if you're trying to transport this between different languages. And that's why I find state machines so powerful is that you could declaratively represent your application logic without tying yourself to implementation details of which framework you're in. Yeah. This is so, this I mean, this seems like super powerful stuff. I think I'll have to actually play with this a little bit to keep my brain from bubbling out my ears here, but [laughter] th- th- like this looks really cool and I'm I'm really into this whole idea of like being able to do cool things.
1:03:00 We are doing a I think next week we're off
So, this would be like if you for example, were to make a request to a an API that doesn't always resolve, you could just say like, "Hey, if this fails, we're going to we're bounced to this state, but if it succeeds, then take it to the success screen. Right. Okay, that's super cool. And then there's one more question in the chat, which is how soon can we expect that XState and automated testing article? This week or next week, hopefully. So, I have a draft and if you are extremely curious, just hit me up on Twitter and I'll share the draft with you. Awesome. And people can find you on Twitter at @DavidKPiano. Oops, piano, if I can spell. You should definitely go follow David. He is extremely smart and very chock-full of good insights into just, you know, things like state machines and lots of things that aren't state machines that are also smart.
So, yeah. Where else should people look for you? Is there anywhere else that people should follow you? Well, we do have a community at state spectrum.chat/statecharts, where I think Max Stoiber and others made the spectrum.chat community, which is really great and a great way of sharing information. And so, there's definitely a lot of talk in here, just people trying to use statecharts in their view, React, or other applications, whether they're using XState or something else. So, it's a good place to talk shop. Yeah. Yeah, yeah, that is that is super useful. Well, David, thank you so much for coming on and thanks everybody for watching. We are doing a I think next week we're off. I'm going to be at Heroes. This is not the pet channel I wanted to bring up. We are yeah, I'm at JS Heroes next week, so there will be no live stream, but on the 18th, we're going to have Marcy Sutton back on and we're going to do an accessibility deep dive, which is going to be a whole lot of fun.
So, please hit that follow button and check this out. It'll be posted on YouTube once I have the ability to export it. And hit us up on Twitter, both David and myself. We would love to talk to you. Thank you very much and we'll see you next time. Thank you for having me.
Related talks
-
Let's Pair Program State Machines and Statecharts
Fun Fun Function, 2020. 59:06, 23.2K views -
Goodbye Slop; Welcome Determinism (Beyond the Prompt)
Beyond the Prompt 2026 (AG Grid & Bryntum, London). 30:27, 29.2K views -
React Wednesdays: XState and JavaScript State Machines
React Wednesdays (Kendo UI), 2020. 1:09:05 -
Model-based testing with state machines
Web Zurich meetup, 2020. 18:12