Use State Machines to Build a Queue for Custom Twitch Overlays
In this episode, Jason will build a state machine (using xstate) to handle subscription events and display them in Twitch overlays.
Links & Resources
Full Transcript
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Jason: Hello, everyone! And welcome to another episode of Learn with Jason! Today you're stuck with me. We're going solo. I am -- we had to reschedule with Aubi. No big deal. He got green lit for his COVID vaccination, that takes precedence. We're going to talk about state machines and Kubernetes. But today, let's talk about state machines and Twitch. Today we're working on the state machine that powers the Twitch overlays that you are looking at right in front of your very eyes. The lower third. Down here, this whole bit. All of this is actually powered by web technology.
So, it's a Toast site, it's got a GraphQL going based on socket studio, something I built. Someday I'm going to release this thing and it's going to be real. But, you know, in the meantime, what we're gonna do is we're gonna see if we can't keep going on this idea of making this subscription thing work. So, in you remember the last solo stream we did, I but the together the overlay -- hey, thank you so much for the raid. Welcome, everyone. We're going to play with XState today. So, yes. Let's see. What are we doing?
We're gonna make this heart thing work. That's what I'm excited about. I want to give this a try. So, how this is gonna happen is right now this overlay is always visible. It just shows up. And that's okay. Because it's fun. And I like it. It makes me smile. But what we want to do is I want to make it so that when someone subscribes, I can show their name and that they've subscribed and then show this overlay there. So, it will be kind of a fun thing that we can do. I think it will be, you know, I think it will be a cool thing that we can try. And we're gonna try to do it all using a state machine so that we can handle something that I don't like about a lot of Twitch overlays, which is that when you get multiple events in a row. So, for example, if somebody gifts ten subscriptions, then what ends up happening is the subscription animation place ten times in a row back-to-back with kind of a loop.
And that can be noisy. That can be kind of a lot, right? So, instead what I want to do is I want to set it up where we will have a 5-second time out for this overlay so that when it starts, or however long it is, it's 5 seconds, 7 seconds, something like that. So, we have time for it to play through. And if we get additional subscriptions while it's open, I want to add the next subscriber's name on the overlay, but only extend it a little bit more. I kind of to want make it smarter where we have the overlay going and subscriber names pop in maybe one every 2 seconds. And that they we can still show the love and all that good stuff, but we don't end up with, you know, a full minute of the subscription sound and a portion of the screen covered and all that stuff. So, to do that we are gonna be using XState state machines. I'm a big fan of state machines. I use them for a lot of stuff already. That's what controls the chat. It's what controls the other sound effects that come in. So, this the not gonna be new -- it's not a net new addition to the scenes. It is a -- kind of an extension of something that we were already doing. But I wanted to do this from scratch because I think it's so much fun to kind of dig in and see how these things work.
Because, chat, tell me, how many of y'all have used state machines before? Like, who is net new to state machines? How about this, give me -- so, I know it's like an F if something goes bad. What's the Twitch thing if something goes right? You do like a -- is it a W? A couple -- a couple new folks, people have heard the name. Yeah. All right. Okay. So -- so, let's start from scratch here and we'll just -- oh, yeah, Taylor's used them. Okay. Cool. Here's what I'm going to do. I'm going to turn off this lower third here for a while and I'm gonna switch us over into solo scream mode. I never use this mode. I always have friends. I'm glad you're here to keep me company because otherwise I would just be rambling to myself.
Before we start, a quick shoutout, thank you to the sponsors. Chat, my rambles are being live transcribed by Amanda who is here with us today. Thank you so much, Amanda. I -- I appreciate you being here. She's from White Coat Captioning. White Coat Captioning does all of this every week, every show we do live captioning. You can watch it right on the home page at learnwithjason.dev. And we have the sponsors, Netlify, Fauna, Hasura, Auth0 kicking in to make this show more accessible. And that means a whole lot to me. So, let's do this. Let's jump in and talk a little bit about XState. So, if you are not familiar with XState, it's a way to do state machines. And so, state machines are -- oh, wait, my subscription thing is still visible. Why is it visible now? You're not supposed to still be visible. What's going on here? What have I done? I think I might have just like straight broken this one.
Oh, there it goes. I have two of them. That's what it was. Okay. Cool. So, I have -- I have my subscription overlay down here. And this one is actually the one being pulled in from Socket Studios. This is -- oh, it's not active. Oh, interesting. I wonder what's gonna happen if it -- could we hook this up to a chat or a boop or something? Somebody boop, see what happens. What X State is, X State is -- it's a library for writing state machines. I'm probably going to keep the docs open all day because that's the way I roll. And we're going to figure out how to make this thing work. I'm going to take a look inside of my existing scenes. Not GitHub. Go to the scenes. These are the scenes that are actually overlaying the stream right now. And so, when grow and look here, you can see that this is a Toast site. So, we're using -- let's see. We've got Toast under the hood, we're already got XState installed. This is how we're sanitizing the HTML so that you all can only do marquee tags and not the other nonsense that you were causing the other day. I love that you did that, Nicky, never stop. We've got a couple other things in here. We have matter-js, this is what makes the boops drop when you call them. This is the physics library, polyD comp is part of that. And then xstate/fsm, the smaller version of xstate. And then React. And I was trying to use fsm and I forgot to clean you were after myself. We have to figure out which one of these I'm actually using.
So, to do that, let's look at one of these state machines. Let's look at -- let's look at the hooks. And we've got a boop. Okay. This one is not using state machines. It's a had not even why -- why would that be a state machine? Here's a state machine. We're using xstate React. And so, to look at one of these machines, it is defined like this. We set up a machine. And then in it we set some details like an ID, what the initial state should be, any context that we need to pass into it. And then we create states. And so, each state has a name, like idle, that's what we're using here. And then we let actions happen. So, on a chat message, we perform the update count action and we move to updating. Then when we're updating, we do more actions. This means like do this thing immediately so that it runs a conditional and then moves to counter visible. Then it does some other stuff if the count is over the trigger, then it will move to starting. And otherwise, it goes back to idle.
Then the counter-visible runs this function. It invokes a function. And when it's done, it goes to hiding. And so on and so forth. So, you can kind of see, like, we're defining very clearly how all of these things fit together. But the reason that this is so -- so exciting to me and so kind of powerful is that it means we can do things like actually visualize what's going on. So, if I go to this visualizer, we can take a look at a state machine. And so, when we look at this machine, we can see that we've got, you know, here's our machine. And it's got an ID and some context and then it's got these states. But the state has an action of fetch. And that's shown here. We can see it. We're in the idle state. Now we have to fetch. If I want to fetch, go to loading. Now in loading, we have two nor actions, resolve or reject. I'm going to resolve. And that takes me to success. And success is the final state. Once we get here, we're done. That's kind of the power of what's going on. I see y'all have working hard to try to hack the chat. So, I cleaned this up as much as I could to make it so that I'm pretty sure the only thing that still works is marquees. I think that's it. So, all right. There's a question from Nicky. What is the difference --
You hackers! You dirty hackers!
There's a difference between a machine and new machine and I don't know that -- wait. Is there a difference between machine and new machine? Did I even create a machine? Did I create a new machine? I don't think I did. Let's look. No, I -- I don't think that you call it like new machine. I don't think it's a class. Oh. It is. Oh, David's here, what's up? We should have known. I said "State machine" more than three times. This is good. I capitalized it for no good reason years ago. Perfect.
So, let's see if we can actually kind of write this up. Because what I want to do is I want to -- I want to make a queue. And so, we're gonna call this one, like, sub-queue. And our initial state is gonna be idle because nothing is gonna come in. But what I want to happen is I want -- and I'm gonna -- let's start by just defining our states, right? So, I'm gonna go with just the names of our states. Let's remove everything. I have states. I don't need context. Let's keep our context empty. All right. So, I'm updating. So, here we go. We have a state machine and it's idle. So, the other state that I want is I want -- let's think this through. When we're in the idle state, or when we're in any state, really, we need to be able to receive an event. But that's not a state. That's an action. That's a thing that's gonna happen. So, instead, when we get an event, we want to go to the starting state. And the starting state is going to be where we're gonna start showing the thing. We want to fade it in, we want to have some little animations and stuff. And then, once we have started, we're gonna move over to -- what -- what did we hook up to this sub-thing so that it's popping up? I can't remember. But so, we are now in the starting state.
So, if I update this again, now we have a starting state. You can see they're not connected yet. But we can make that work. And now I could even -- I can do that right now. We can say "On subscription, we will move to starting." Okay. So, now whenever we fire the subscription event, it's going to take us to starting. Once we've started, I want to invoke my animation. And my animation is going to get us into we'll call it the active state. And then once we've gone through the active state, we want to end up in the ending state or transition -- I don't know. We could call this a lot of things. Closing, starting, activating, deactivating. I'll call that ending because, yeah, that makes sense. And then once we're done, we want to go back to idle. So, this one, I think, this is where things are gonna get a little bit interesting because I want these to auto-advance. And so, I think what I want to happen here is as soon as we go into starting, ooo... That's a good question, Linda. This is arbitrary. I could call this whatever I want. I could add a new one. And we'll say boop, and that one's going to take us to ending. And if I update this, now I have a way to jump from boop to ending, right? And so, we're just kind of defining the way that our application moves and we're saying that, you know, in this case, I'm specifically saying that from idle, we can only go to starting or ending. And in my case, what I actually want is I only want to be able to go to starting. so, I go here. Once I'm in starting, I need to get to active and ending. But it these ones are not based on actions, they're based on time. I want a period of time where the animation can one. And then I want active to be in that state for a period of time. And when the time out ends, I want to transition to ending which will be active for a period of time and then it will move us to idle again.
So, what I'm gonna do is I think I'm going to run -- let's make this simple. We're going to invoke. And the source is going to be... let me check out this works. So, when you invoke in the docs... I want to see services. So, that is what I want. So I'm going to get a services here. And there's a reason I'm going to do it this way. So, if I set up services like this. I think it's like this. And then I would set up, let's say transition. And that's gonna be a function and it will set like a -- how do we want to do this?
We're gonna return promise. Let's do a new promise. And that's gonna get a resolve. And I'm not gonna reject this because there's no reject case here. And what I'm gonna do is set a timeout for resolving this. Actually, we can do this like this. We'll resolve after 500 milliseconds so it's easy to see it happening. That's how my transition works. It's going to return a promise that resolves after 500 milliseconds. So, up here, I can run let's say transition. And on done, we're gonna go to active. Okay. Let's see if I did all that right on the first try.
Okay. So, I'm gonna run a subscription. And what should happen is when I fire this, we should go here. And after 5700 milliseconds, it should bounce to active. Let's see if it worked. Look at it go! So, that's exactly what I wanted. And now I'm gonna do the same thing here in ending. Because I want the same thing except when it's done I want do go back to idle. Okay. Now, we're not gonna be able to get from active to ending yet. But you can kind of see here how it gets subscription and then done and then ending and we loop back around again.
So, what I do in here is I can do something like, I guess this would be more or less the same thing. We're gonna invoke in our source here is gonna be display. And then when we're done, we're gonna go to ending. Okay. So, once we're done... let's call this one display. And it's going to get -- did I break this just now? I did. And then I'm going to again return a promise. But let's say I want this to last for -- we're gonna leave this up for 5 seconds this time. So, now what I've got is -- it's a little harder to see. But it should be okay. So, I'm going to run the subscription. It goes in, transitions. We're in the active state, and then the ending and back to idle.
We have a loop. And here's what's exciting about this. Now when we go to define our state, our state is something that is revived from this instead of us having to do a bunch of if/else steps. And let's copy and paste this in. So, we're going to make ourselves a component.
And I'm going to come here. Where am I? What day is it? Okay. Let's go here. And I'm gonna open this. Thank you very much for the sub, Jimena, I appreciate you. And make sure we're using the right version of Node, use 14 because this is a Toast site. And I'm going to close this because we don't need it. Okay. So, in here, I have all of these things that I want. And let's play with... let's see. What's the easiest way to do this that we can actually kind of see -- here's our styles for the subscriber overlay. And then we've got a scene, pages, subscriber overlay. Here's kind of our setup now which is -- it's pulling in an event. And the event -- what event is it pulling in? Using Twitch chat. Oh! I remember what this is. Yeah, the reason this is happening so often is because I hooked it up to Nick Mercs can's channel, and he gets 20,000 -- how many? Let's see how many people are watching Nick right now. It's going to be so many. He's got 83,000 people watching him on Twitch. So, that's why we're seeing the -- any time someone subscribes to him, that's why that little heart thing is popping out right now. That's fine. But what we're seeing is at the moment it's not really running a state machine. And that means that we're just kind of doing this, like, if there's an event, run this. And that's kind of -- this is messy. Like, you can see that this is messy. So, I want to, you know, make that better. I would like very to not -- not be doing this kind of if there's an event, then we're gonna throw the thing. And I don't know. It all seems bad. So, I don't want to do that anymore.
So, I'm instead going to -- let's... should we keep Nick Mercs so we've got more events? If we look at the actual scenes with which is at -- where is this thing? Subscriber overlay. Right? So, this is the actual overlay. And we can see in here that there is the Toast page section and nothing is in it right now. But once we get a subscription, it will show up in here. But what I want to do is I want to -- let's make this manual instead of -- instead of using the Twitch chat at all. So, maybe -- maybe instead of trying to build it in here, maybe we just start by building a test page. So, let's call this state machine. And that way, when we run this, we can just, you know, load it. And then I'll make a button for it. So, we're gonna import -- not over there we're not. Import h from preact. And then we'll export default StateMachine. And that one is going to return for now a div. And inside the div, we'll say h1 -- State Machine Testing. Okay. Now, up here, I have that -- nope. Not that one. Here's the code that we set up. So, I'm gonna drop this in here. We'll drop out these bits that we're not -- that we're not using from the visualizer. And for now, I think we're going to comment these out because we're not quite showing them yet. Okay. So, what I should be able to do now is I should be able to start this. And I start this... how? I start it by running...
Dev. There it is. Okay. So, npm run dev. Actually install everything... oh! Duh. I have to write valid code for everything to work. Okay. Let's try that one more time. What's that? Do I need to install? That looks -- that looks more promising. It's doing the thing. Installing the stuff. Everything is built. And an npm run dev. And it's still unhappy because... why? Doesn't like... cannot read property, "Replace" of undefined? What does that mean?
Wait. Is my site just like broken right now? And I -- that's gonna be fun! Cannot read property, replace of undefined -- where? Window. Oh, my goodness. Oh, am I missing -- oh, my god. I know what's wrong. Y'all, I forgot that I need my environment variables. I got to run this in Netlify Dev. Okay. So, now because I'm using Netlify Dev, it's pulling in my environment variables and I'll be able to actually run this. Which means that it's not broken anymore. Which means we can go here. Not over here, though. We're going to pull it over here. Let's get this on this window. And then let's go to State Machine. Okay. State Machine Test. Here we go. We are now in business. We have a State Machine for testing. We are going to hang out over here for a while and try to get this thing breathing.
So, what I want to do is I want to import useMachine from xstate/react. And I also want to import Machine from XState. Okay. So, then that means I can put my machine in like so. And I can state, I think, from useMachine -- I could look at the docs here because I'm pretty sure I'm doing this wrong.
SubQueue machine. And that's wrong. There! This is almost assuredly wrong. I'm going to look at it before I embarrass myself. Let's get to -- let's get to the docs. The docs are here. We're gonna go down to the React. And we've got use machine. Let's jump down to useMachine. So, we pass in a machine and we get back -- ah! So close. I was so close to having that right. Okay. So, we're gonna get the state and the send as an array. And... let's do this. We're going to pre-- JSON.stringify our state. And then down here, a button, and onClick, we will send SUBSCRIBE.
Okay. Let's go! All right. Should we try this? I'm ready to try this. So, I'm gonna come back over here. Let's go back to our local host. And I'm going to stop and restart. What did I do? Now what? Don't you yell at me! Cannot read property, undefined. That doesn't sound right. Am doing something differently? Let's look at -- was it this one that had a state machine in it? Yes. UseMachine from xstate. Maybe I just don't need -- it looks like I don't even need the import h anymore. So, maybe I just skip that part?
Let's try it. Nope, doesn't like that. I'm going to npm install one more time to see if it gets packaged up in a way that I'm not letting it do it. Anybody? Bueller? Okay. So, that's doing a thing. Are you gonna do a thing? What are you mad about? So, it's -- pages state machine is going wrong. And they can then can't do one of these, cannot read H of property, undefined. Did I add something in here? Transition is a promise. This is a promise. Hm... Got my machine. That's all the same. States are all the same. Hey, what's up, FaradayAcademy. Thanks so much for the raid! Welcome, everyone. It is really good to see you here. We are arguing with state machines right now. Well, actually, we're not arguing are state machines. We're arguing with my website. The state machines I think are fine. I'm trying to figure out what I did wrong that's causing this to -- to argue with us. And as far as I can tell... I've done nothing. Nothing! Why are you like this updateCount -- does this need to be a -- no, that's an action. That's what we want. So, what the deal is?
What are you doing, computer? Why? Why are you like this?
Well, yeah. Yeah, yeah, yeah. What do you think, y'all? What do you think is causing this problem? I'm getting this preact hooks. We're not using any hooks, though. Like... maybe I need to use fsm because I'm pretty sure the API is the same there. Let's npm install. I don't have a repo command. But that's a really good idea. Let's share that repo here. This is -- this is what we're working on today is the scenes repo. This is a collection of things. We're also working on this -- this xstate visualizer. And I'll show you something cool here. Once I get this all running, I can log in. Right? I'm logged in. And then I can save this. Now look. I can send this to all y'all. Isn't that cool?
So, now you can go, like, play with that and edit it. And then if you logged in with your GitHub account, you can save it and pass it around and we can all kind of see what's going on. Now, why this is -- why this is doing the thing that it's doing right now is a little confusing to me. I'm unclear on what the -- the issue is.
Let's see. Are we getting a different issue at least? Named export use machine -- okay. Well, that's a different error, at least. Let's go look at fsm. I could have sworn fsm had a useMachine. No. Oh. Oh. Oh. I know what's going on. UseMachine. So, fsm is like finite state machines, it's a much lighter version. XState does a ton. It's got different patterns that you can do, you can do hierarchies and guards and services and all this stuff, right if and the fsm package is similar, but it only does a limited number of things. Which are the most common things. So, this is kind of the 80/20. But the benefit is that the library goes down so 1 kilo byte which is nice. I was thinking I needed something that wasn't available. Let me turn this off because it's getting distracting. I'm going to turn that off so you can actually read the docs instead of looking at my silly face. Hey, Chris! I might need your help. I am -- I am struggling here because I -- I'm getting a very odd issue that I don't know what I've -- I don't know what I've done.
So, check this out. So, my Toast site. It doesn't like... this. But I'm so confused because like, this exact code is working over here. So, what have I done that's causing it to break?
Let's see. I don't think there is a JSON command. There is -- let's see, there's a Tals command, a couple others in there. What have done? What is different? And why doesn't it like -- why doesn't it like me? So, let's think this through. Yeah. H sounds like compiled code. And the part that's confusing to me is why it's doing this thing. I will -- like... because we've got... pretty default setup here. And I'm just trying to figure out why it doesn't like this thing. So, it's -- hey, what's up, Henri? Good to see you, thanks for hanging out. Let's debug this bit-by-bit here. Let's find out if this is a problem with like preact not liking something that we've done here, or if it's -- it's something else. The part that I'm wondering about is maybe I just need to move it into a hook, right? Maybe we can just -- we can do like use-subscriber-queue. And then we can do exactly what we were doing over here.
But the difference is, is instead of returning a -- a component, we'll say, useSubscriberQueue. And that's gonna use our state machine. And then it's gonna return. For now, we'll just send in state and send. We'll probably want some details there, right? Because we're going to hook this up to our other components. But here what I can do is I can import useSubscriberQueue from hooks. I use subscriber-queue. And than let's export default function StateMachine. And we'll return -- I guess we can const state, send. And then that will be useSubscriberQueue. And then we will return, again, a div, h1, State Machines. And then let's just -- maybe let's see if we don't even -- let's not even try to render them. Let's just log 'em.
And like this one should be a function. This one should be an object. And we'll just -- we'll just see what happens. So, let's try that again. Do I do YouTube reaction videos to music? I don't. I... sounds like fun, though. Let's see if we got any logs, though. So, this is still exploding. Now it's exploding -- now, let's see, that's preact dist hooks. Is this even the same code? Like, why are hooks suddenly failing? Use constant, use machine, use subscriber. Like, did something change -- you know what I'm gonna do? I'm gonna remove Node modules. And I'm going to remove -- remove this public folder. And I'm gonna remove temp folder. Get it all out of here. Let's just clean it up. And now I'm going to install again. And let's see...
JavaScript reaction videos. I don't know about that. I don't know about that. Oooo... the breakfast burrito, though. I could get down with that. That sounds like my kind of content. Just a sing along to breakfast burrito. I'm going to play like 1 seconds of this so I don't get a D M CA takedown. This song kills me. It makes me so happy. Wait, how do I get past the ad, though? Wait, I'm going to the wrong place. Let me -- let me -- okay. Let me get my sound up. Go to the right place here. Here we go.
All right. Y'all should be able to hear this. And it's gonna be the best and worst thing you've ever heard.
�� yum! Yum! Yum! � �� yum! Yum! Yum! � �� yum, yum, breakfast burrito �
Jason: Anyway, I don't want to get my channel muted. I love this song so much. I make it as my alarm and drives my partner up the wall. She hates it so much. I'm going to put that in the resources there so everyone can enjoy it. Let's figure out what's going wrong here. What's going wrong with my setup that this isn't doing what I want it to do? Tenth cut of you dancing -- raining tacos -- the whole catalog is outstanding and worth checking out. Let's figure out why I can't get this to work. And I feel like it's gonna be something... right in front of my face.
And I'm just not gonna -- I just -- I don't know what it is. Like, why would it not work here? So, like if I -- if I take all of the machine stuff out, right? And I take this out... and I just return, no and absolutely not. Then this component should log no and absolutely not. And it should all just work. So, let's run it and see what happens. So, now it's mad again. Something went weird. Something got like messed up here. So, now it's logging. But then h is not defined. So, okay, do I need to -- to add h here? Okay. Okay. We're a step closer. You could say, one step closer to the edge. We're not about to break. Well, I'm about to break this code.
Okay. So, let's try something else. We're gonna get -- we're gonna get... let's just put it all back in and just see if maybe it was -- maybe it was the thing. Maybe it was just like we needed to get the pragma in and move this loop out. Let's try it again and see what happens. I'm gonna run it now. Okay. Now it's unhappy again. And it's unhappy because of this H problem. Again. And so, it's coming out of dist hooks and then it goes into useRef. And we're not using ref. And then it gets into useMachine. And that's xstate. And then it gets into my use subscriber queue. So, what is it doing?
Why?
Strike loader... [Heavy sigh]
From a heart point of view, it's literally right in front of your face. This error is definitely right in front of my face. So, we gotta, we got to figure out why. Maybe I shouldn't overthink this. It seems like something unlike the problem we're trying to solve. Maybe just create React app for this? Let's do that. We're going to pull this up, npx, create-react-app and we're going to call this one subscriber-overlay-queue. And we'll just build everything in here and figure out what's going on.
But yeah. So, we'll get the -- we'll worry about the React stuff later. I'm sure I'm doing something silly here that is not related to what's -- what's going on. Machine -- as far as I can tell, machine's getting the right -- we're about to find out when I run this in a plain React code. My thinking is something in useMachine is arguing with something in preact. That's not what we're here for. We'll deal with that. When trying to render send, well, it was getting with the error before it was trying to render send.
So, let's run this test and we'll see what happens. So, I'm gonna take this bit. Oh, buddy!
Can I get a please hold in the chat? Here we go. [Hold music]
Yeah, Chris, I'm literally using it in another component here. Like the -- the -- the layers of confusion I'm feeling. And I'm wondering if maybe something bumped like a version or something and I just don't -- I didn't see it. But whatever's going on, something has gone strange and I'm going to fix it. But I'm probably gonna do that not where everybody has to watch me pound my head against a keyboard. So, instead, let's do this subscriber-overlay-queue. Open it up here. And I'm going to... just simplify this app.js down to our original thing.
No, I didn't install anything extra. I'm like -- well, I -- I ran -- I like, cleared the Node modules and did another install. I didn't change the lock file, but it would have been a minor version that would have caused the issue if it was anything. In fact, I'm going to take the simplest possible execution here. We're going to take this. And we're going to drop it in -- where am I? Here. We're gonna drop it in here. And we're gonna get rid of this part, right? And so, then, I'm gonna get rid of export default function, state machine. Let's just change it to app so that it doesn't complain at us. I don't think it will. But just in case. And then we'll render that. That all seems good. I will keep -- don't need this file anymore. Don't need this file anymore. I don't need this file anymore. We can keep those. That's fine. And then I need a hooks folder. So, let's call it hooks. And use-subscriber-queue. And then in here, we're going to actually drop in use-subscriber-queue. Okay. And then this one is going to change to be that directory. Okay sop, now we're all happy. Everything is cooperating. And I'm pretty sure if I go into -- if I just run Netlify Dev, I think it will work. Might have to link it.
Where did it get those environment variables from? Okay. Can't resolve xstate react. We're going to install xstate/react and we're going to install xstate. And while we're doing that, Chris has an idea. So, I'm going to zip up the whole thing. GitHub/learnwithjason. And let's do a -- oh, boy, can I do it? Tar -czf? Scenes into -- no way this works on the first try. No. That is not how that works. Okay, I'm gonna do not that. I also should probably remove my -- my Node modules before I try to zip things. So, let's jump into the GitHub folder. And Learn with Jason and, scenes. And we're going to remove this Node modules. And then we will zip the whole thing. All right. Chris, coming at ya in Discord.
All right. In the meantime, we have installed anything. So, I'm going to Netlify Dev again. Nope. That's not what I wanted at all. You're installing over here.
Ahem.
What? Don't delete -- I'm sorry. I already did it. I'm going to fix it. I'm going to fix it right now. Because I have the alias. So, when I remove things -- 10:22:00 a.m. There's my Node modules. Look! I can just put it right back in. I did it. Okay. And then I'm gonna change this back to Node modules. And then I'll zip it for you again. Okay. Compress. This is going to be so much bigger. 67.8 megabytes. I suppose it could be worse. And now it is how big? And 21.2? I think that's below the Discord limit. I think that worked. Those are installed, and I'm going to Netlify Dev again. And... are you doing the thing? Do the thing. Nice.
Okay. So, now we've got these. And we're getting back a state -- yeah. So, it just worked. So, I did something -- I did something goofy. But that's okay. Because we're going to make this thing work. So, now that we've got that out of the way, we are going to get to our... close this one up. And we're going to put this over here and let's -- let's set this thing up to actually show a button. So, we're going to display our state, stringify state, null, 2en and then we'll add a button. And our button is going to -- Send Event. And it will onClick, send -- nope. Send SUBSCRIBE. Okay. So, all that being said, we can see now that the state that comes back, we're in idle, all those things. And so, let's run through this. What I click this button, what should happen is we should start to see the state start to update. It didn't do it. Why didn't it do it?
Idle... I typo'd this? Sub queue -- subscription. Not subscribe. You know what? Let's make it an action. It is an action. When we have subscription, we subscribe. And then I'm gonna click this button. Do I need to reload? There it goes. Look at it go! Look at it go! Look at it go! It's doing its thing now. And we're in the value and each of those things happens. And this is the part that's so cool about this. If we take this out and we instead just set some defaults here. Like, we can do something like let's say -- oh, I don't know. We can say, if state value equals starting... we can return starting. And if state.value equals active, we can return, hello!
If state.value equals ending, we can return ending. And finally, if state.value -- actually, we could even leave this one default. But we could do something like, if it equals idle, then we're going to return this button. Okay. And then for the sake of making this a little more -- like a little less aggressive, let's do it like this. We'll do a const like StateThing. And we will do all of this in here.
And then down here we can... add our StateThing. Okay. So, now we've got our StateThing. Starting, hello. Ending. And send event. For the sake of not having to watch that for so long, let's make it like a second long so that we get, you know, it will be fast. But not so fast that we can't tell what's going on.
Yeah. That's -- that's acceptable. But yeah. So, only one of those conditions will pass at a time. That is -- that is correct. So, we could, if we wanted to -- and like, yeah, we could do this as a switch statement. We could do this as any number of things. We could also if we wanted to take this part and put it into the root of our app. And then pass in like... I don't know. CurrentState. Do something like this. And then we have... currentState. Be able to drop that part out. And then all of these.
Oops. One too many.
And then like down here we could set up, I don't know, a function. We'll call it function, you know, show Overlay. And this one will call send SUBSCRIBE. And then we can do -- we can do, like... equals showOverlay. And now we've got this function over here that we can pass in. And we can drop that in here.
And now, like, our component doesn't care about state machines. Our component just cares about the current state. And so, we can kind of set all this stuff up here. And the outcome is the same. We get this really kind of declarative way of writing about how things work. And the thing that's really nice about this is, you know, this -- this feels like the same kind of power-up that we get when we've got, like, you know, GraphQL already one you have the info, and write it in the query and execute it in the browser and it does what you want. And then when you're happy, copy and paste into your app and the app just works. But it is really, really cool.
Oh, yeah. And David's making a good point. Like, yeah. Like this -- and we could -- we could absolutely if we wanted to do something like, how could we do it had? We could say, like isStarting equals state.value starting. Right? And then we could pass these in as like is starting, is not. Or, like, I could do it inside of here. So, we could put out is starting/is ending and active states. Something like that? But I like the idea of just saying we map our views. What we're gonna see is the thing that we see here. And I think that's really cool.
Another way that we can do this, and the way that I like to do this that's pretty -- pretty fun is we can use this instead of updating the whole view, we can use it to update classes and get animations and stuff. So, let's -- let's do that. And I'm going to import useRef. I think that will be enough. Let's -- let's do that. And like -- I'm gonna set up we'll say app.module.css. And in here, we'll say -- we'll call it overlay. And this is gonna have a -- we want to make it kind of obvious that it's a thing. So, we're gonna give it a border of 2px solid black. And a boarder radius of we'll say like 0.25rem. And a background color of, let's go hot pink. And a color of I think -- I think that will pass standards. That might be a little intense. So, if it's too much, we can tone it down. Then, if we've got an overlay hidden, we can set the opacity to zero. And then -- actually, let's do it the other way around. Let's do visible. And we'll default the opacity to zero. And we'll set the transition of opacity to I think it's 500 milliseconds. And we'll go linear.
Okay. So, now that we've got this, I can pull this in. I'm pretty sure that just works in React. So, import modules from app.module.css. Find out if it's going to work. But now what I can do is I can get my ref and we will -- instead of doing... yeah. So, here's what I want to do. So, instead of doing the return like this, we're gonna return a box. And so, our div is gonna have a className of styles.overlay. And it's gonna have a ref of ref. And inside, we're gonna say, I'll just put something in here. We'll say, HOORAY! Now that we've got that, if I update and save this, let's make sure that the styles actually worked. Can't resolve app.module.css. That's because I put it in the wrong place. How about that instead? So, we've got our -- yes. So, let's also add the visible class.
Or, actually, for now just so we can see that it's working, let's make that happen. Okay. So, there's our thing. We can probably add a little bit more like... go max-width of let's do 500 pixels. That -- that's not 5700 pixels, is it? I guess it was. Okay. And then we'll, like, text-align: Center. Okay. So, that's -- that's kind of what we're after, right? We want something like that to work. But by default, we want that to be invisible.
So, I want when our -- when our state changes, so, I'm gonna useEffect. And we're gonna trig their on currentState. I want to change the classes. So -- so, what we can do here is we can -- let's see. We can do this a few ways. I'm going to -- what I'm gonna do is I'm gonna say if currentState equals... starting? Or currentState -- actually, you know what? I'm going to do this the way I like better. Mrs. to get an array of -- like that. And then we'll say... includes? Yeah. Got it first try. CurrentState. Then what we're gonna do is we're gonna do ref.classList.add, and we're gonna do styles dot... styles.visible. Okay. Otherwise... we will ref.current.classList.remove. Styles.visible. Probably a more elegant way to do this. I'm not going to stress about it too much. But what should happen, we should see when we go our state here... oh. And then -- then what we can do is we can say, if -- and it will be starting, active, ending. Includes currentState. Let's make this bigger. Let's make that more visible. Then we're gonna return this bit here. Otherwise... we're gonna return our button.
Okay. So, let's see if all of that code that I just wrote works in the -- it definitely does not. Ref.current is null. And that's because this needs to be visible no matter what. Of course it does. That makes sense. So, I actually don't want that. I instead just want this. But I also want... you know what? I probably want these to be separate, don't I? Because this should be the overlay. And then I want my button down here. So, my button is gonna move. I'm gonna put my button down here. See that? So, now what we've got is we've got a State Machine-driven kind of animated element that will let us go in and out of -- of a given state by applying classes. And it just starts to feel like this -- this to me feels really nice. Because it -- it has such a, you know, it's very declarative. Like, I would like to start the subscription event, please. And then I send in my state value. And then I can say, you know, when the state changes, if it's one of these, I would like it to be visible. Otherwise, let's remove that. And because we're using a ref, it doesn't hard-refresh the whole element. And that means that we get these -- these kind of back and forth here.
And if we go in and look at the state machine, like the state machine is very explicit. I can't, like, if I click this, it won't do anything until it gets back to that event. And actually, we can even handle that. I can do a... let's disabled equals state.value does not equal idle.
Okay? So, now when I run this, it's disabled until you can click it again. Boom! Done. We could also do something like down here... we can wrap this and say, if it does not equal idle, we can say showing overlay. And otherwise we'll send our event. And so, now when I click this, it says showing overlay and then it sends our event. And all of this is so much simpler because we have a state machine in place. And this is something that like when I first started looking at state machines, it was like, you know, you see the stoplight example and you're like, cool. I get that. And you see people showing like really advanced examples and you're like, I don't get that. It was when I started seeing these kind of UI controls and how the UI really starts to feel declarative that I started to feel like I get this. Like, I see why this is so valuable. Because, holy crap, look how little code we had to write. Look how little logic is involved to do this very complex thing where a button is changing our state and we cannot click that button again until our state resolving to where it, you know, the button is clickable. That's a lot of logic to write on your own.
But with state machines, because we have done this really clear definition of how state machines kind of interact with each other, we -- we can see what it's gonna look like. And we see exactly what state there is and where things can go. That makes for a really, really powerful programming experience. And like it also means that if I'm gonna talk to my team about how any of these things is going to function, I can show them. I can say, hey. This is what the app looks like. Do you see any part of this flowchart that doesn't make sense? Because, you know, we could see like in here, for example, if I wanted to map a UI, I could say, I'm on the home page. And then I submit my email. And then I go to a -- a like, you know, I need to see the please confirm your email. And that waits for an event from our server that comes over and triggers the email confirmed event. Which triggers the you're confirmed and then checks if I have gone through the onboarding wizard. And all can be handled in a state machine like this. We're looking for different events that are different things.
Alan, yes, you can have multiple states. American 2050, I haven't done the queue part yet. We have about 18 minutes. I burned a lot of time trying to get the thing working. But fortunately I have built a queue with xstate before. I'm going to go to my GitHub and try to find this. Because I do want to show it because it's very, very cool. See if I can figure it out. This is my queue... that's not it. Let's look at -- let's view 'em all. Machines. Transition speed. This might be it. Command received. Nope. That's not it. Keep going. One of these works. But I definitely do want to show you this because it's so cool when it works. As you can see, I do a lot of these.
There's our sites. That's not it. I want my queue. I have a -- I have a functioning queue in here. That's not the queue. Now we're getting too far back. Why can't I find this? I want my -- I want my stuff, though. Okay. Can I see my secret stuff? Why? I did this. I swear to god it exists, y'all. Let me see because I Tweeted about it too and it was really cool. So, let's do xstate queue from me. Here we go. Okay. So, this is -- this is the thing that I built. And there is a link here! Ah-ha! So, this is -- look how simple it is! But here's what's really cool about this is this -- this is basically building an actual queue. And so, let me get this command that will -- that will run here.
And so, walking through the way that this works, we start with an idle command and then we run and then it goes in and gets the command assets. And at any point, you will run the queue and it will fire an event. So, let me do this. Let's -- let's do an event. I'm going to fire an event. This is another cool thing about xstate is if we do it this way. I can run my event from here. Oh, I need to send it. Not a click run. I send it, and it's active. And then if I go in and add the queue, I'm going to send and then we'll queue another one. And we'll queue another one. And queue one more. And now check this out. So, these are all in our queue. And if I look at the state, I can see the queue. And you see them running through here?
So, basically this is -- this is a state machine-powered queue. And it's -- it's set up to make that work. So, this is -- this is definitely like pretty -- it's a little more intense. But to look through at what this actually does, we have like we're formatting, we're handling queue. So, I broke the state machine up. And I added this action because we use this action everywhere. And what this one does is assigns to the context. An assignment is like a context-specific thing. Let's look at a simpler one. When we have our context, our context is, you know, like context in React. It's a thing that's available in the different states and we can mess -- you know, make all those things map. And assignment is a way to kind of set the context without having to actively move through.
Yeah, David, we should -- maybe -- hm, hm, hm! Maybe. I'll harass you after this because it might be time to do a what's happened since the last time you and I talked about state machines because I don't know anything. But in here, when we're -- we run. So, again, we have our states, our idle state. And when someone fires the run action, we run an action. Where we update the context. And so, in our context, we set the current command to be the format -- to be the formatted command. It takes a message and turns it into a command that I can use in the stream. And then once that is finished, it runs to loading command assets. But check this out. So, then in loading command assets, it has handle queue as its action. And so, any time that add to queue gets called anywhere in here, so, I'm running handle queue on all of the states. If you fire this add to queue, it adds to the context into the queue a new command. So, it basically puts it at the end of the array.
And then when we get down toward the bottom, when it goes to stopping, we check for queued commands. And so, this check for queued commands is down here in the actions. And where did it go? Check for queued commands. Where did it go? Delays. Check for queued commands. Oh, wait, check for queued commands is here. And it says -- so, it look -- even when it's checking for queued commands, you can queue new ones. But it immediately runs a conditional, if there's more than one -- or more than zero commands in the queue. Then we start the next command. Starting the next command takes you over here and changes the current command to be the first command on the queue. And then changes the queue to be the commands in the queue minus the one that just got added to current. And it runs loading command assets and moves back up here to loading command assets and then we go to starting. Great. We go to active, we go to stopping. And on the command end down here, we remove -- let's see. We resolve with ended. One of these in here, remove it from current -- we reset current to empty.
That's the power here. Is that when we get to the end, we're able to like replace -- we're able to do all of this stuff, right? And this then becomes really understandable from the standpoint you have our code. Because we're always going to be in one of these states of active stopping, et cetera. And when these queue through, you know? We are able to kind of respond to this much more clearly where I can say, all right. So, if we are starting, active or stopping, I want things to be visible. In any of these other states, stuff is happening, but I don't want to show things on my screen. And then if I'm in starting, I probably want to be adding a class to my overlay so that it fades in. And on stopping, I want to be removing a class. But if we -- if question get to stopping, and we check for whether or not there's another command, then what we could do is we could short circuit that class and just move over to active again so that it stays in place and actually keeps up there. So, that's where I'm kind of headed in the end. Is I want it to get into this starting and active, and then if more subscriptions come in at the same time that the active is going, I want to slightly extend the amount of time that active stays true so that we're able to -- yeah. This is kind of where I'm at. I want to make this stuff work.
I do think -- I agree, it's a little hard to read config-based stuff. What has helped me with this sort of thing is recognizing that the -- the alternative is a bunch of imperative code where I'm setting up a bunch of if/else statements and I'm calling functions from other functions. And it ultimately ends up being a little more fragile. And like one of the things that David says a lot when he's talking about state machines, you want to make impossible states possible. We all say that's true when we look at our code and impossible states are possible. But there will be a way that we're firing up something and we've handled like it's active. And we've handled what to do whether a new thing comes in. But we haven't had to figure out what happens when both things happen at once.
And with xstate, unless we've explicitly defined it, then your UI can't be disrupted by an event coming in that's unexpected because it would just be ignored by the state machine. Now, you want to fix that bug. But you find out it's not working, when I click this button, it doesn't do what I expect. As opposed to when I politic this button, the whole UI exploded. You're putting up guardrails in which is when the UI works. And if it's unexpected, you can fix that. But it's an opt in as opposed to all chaos is possible until I've explicitly guarded against chaos. And I think that's -- it's an inversion of, like, where your expectations lie with code. And I think that that is a -- yeah. I think that's -- that's the sort of thing that we're really after, right? But, yeah. So, I think like this -- this is why you think this stuff is so powerful. Is like this chart is a little bit wild. But it's still logical. We can follow it through what's going on. Run a command, load some assets. When it's done, move over to command loaded assets. Each of those go. And let me send you this whole thread. If you want to try this yourself, there's some fun here. But, you know, you can -- you can take this run command.
And let's just trace it through. Because it's actually kind of fun to watch. It's kind of like setting up a marble game. I don't know if you were the same kind of kid I was, but I liked it as I was a kid to set up my marbles with the tracks and you would drop it through. It was kind of like the game Mousetrap. You get your Rube Goldberg device set up. And we're going to move from run, go all the way through the machine and end up going to the length of the command, which is 10 seconds. And then it's going to go to stopping.
And it loops back around, right? So, each of these is -- like it's just very cool. What's up, Ryan? My day is going great. I have been doing state machine stuff. It's wonderful. So, that, I think, is bringing us up on a pretty good stopping point, I think. I have 6 minutes left. I don't know what else I would be able to do in 6 minutes. Chat, I'm going to start tearing us down. Do you have anything you want to look at before we stop? I think David is in the chat, if I have a wrong answer, he might have a right one. But in the meantime, yeah. Make sure grow and -- oh, wait.
Oh, the issue is with xstate react not shipping VS modules, causing two versions of preact to load. Okay. Let's dig into that. We can make that work. In the meantime, thank you for the corgi stampede and the boops and everybody who hung out today. Make sure you keep in mind, we have live captioning, every week, every show. Today we have Amanda helping us out. And that is coming through White Coat Captioning. Through the support of our sponsors, Netlify, Fauna, Auth0, Hasura. Thank you so much for making the show more accessible to more people. Check out the website. We have so much coming up. Next week, Ben Hong is coming back. We've had Ben a few times. Next week, Vue 3, Hasura, and serverless functions. Ben is a riot, Vue 3 and Hasura and serverless functions are super-fun. Full stack stuff. That's a blast. And I have Sunil Pai next week. And learn about esbuild. If you haven't tried it, it's absurdly fast for building code. It's different from what we're used to with systems like Webpack. Learn why and how that works and what it means for potentially how we're going to build apps in the future.
We're going to make some video with React code with Jonathan Burger. That's going to be wild. I've seen the samples. I still don't get it. I can't wait to learn. Jennifer Wadella is going to teach us Angular stuff. I'm about to do a bunch of Angular for work. She's going to save me from myself.
That's all we've got today. Check out XState. Huge thank you to David for hanging out today. Go and follow DavidKPiano on Twitter. Or if you want to talk to David, just say XState three times. He'll show up in your mentions. I'm sad we didn't get to write the queue, but happy we got the state machine running. I will push this repo up. It will be in the show notes. Thanks for hanging out, y'all. We're going to find somebody to raid. See you next time!
Learn With Jason is made possible by our sponsors: