skip to content

Build UI Components With State Machines

The new Zag component library offers UI components powered by finite state machines. Segun Adebayo will teach us how it works!

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 on the show we're bringing in Segun Adebayo. Thank you so much for joining us. How are you doing today?

SEGUN: Yeah, thank you for having me, Jason. I'm doing pretty good. Such a huge privilege to be here to showcase something I've done. So, I'm definitely thankful for that opportunity. So, thank you for having me.

JASON: Yeah, yeah. I am absolutely thrilled to have you on. We've been trying to connect for a while, so this is very, very exciting. We had a little bit of an issue with the embed. That's definitely not going to make it to the cut, y'all. So, if you're watching on YouTube, you have no idea what I'm talking about right now. Segun, for folks that aren't familiar with your work, you've done a ton in this community. Can you give us a quick rundown of who you are, what you're about?

SEGUN: Yeah, for sure. My name is Segun Adebayo. I prefer people call me Sage sometimes, because I'm a huge anime fan. So, I love anime a lot. I watch lots of anime. I am the creator of Chakra UI and Zag. Chakra gives you all the building blocks you need to build an accessible app real fast. Recently I launched Zag JS, which is an offshoot. So, basically, Zag JS is an UI component library, powered by state machines and state charts. So, that's about Chakra UI and Zag. I'm based in Mumbai. Huge talented folks in there. Very recently, I've been thinking about and really been passionate about building tools that help bridge the gap between design and code. So, that's about me.

JASON: Yeah, you're in good company with the work we're seeing with Figma just dropping a bunch of new things today, the work you're doing with component libraries like Zag is really, really interesting. We just had an episode, or we have an episode coming up, where we're going to look at importing code directly into Figma from the Backlight team. So, there's just really interesting stuff coming out of this space right now that I'm so excited about. What led you to create something like Zag?

SEGUN: It's an interesting journey so far. Launching Chakra UI in 2017, that's what we call the prime-time of this journey. Maintaining Chakra the past three years, keeping it active, fixing bugs, adding features, new components, and people request to that, can you add a speaker to that, can you add more complex components, like a carosel, and I got to the point where managing the complexities got pretty huge, and people want different components, different props, different ways to figure the components to match their only use case. I feel about it very deeply, and at the time, Chakra UI view, which was a view of Chakra UI, and one of the things I started to see from the creator of Chakra UI was basically it copy/paste from React. See the hooks to continuous terminologies, and works basically and you fix a few bugs. I've seen them doing this over the past few months copy over and React, pace it to view, and make it work. Sort of struck me that we might be -- literally be spreading ourselves out too thin here. Because for every single bug we fix in React, does the same in view, speak up with communication to say, hey, view guys, fixed this bug, can you fix the bug is fixed. Yeah, it's fixed now. And we move very fast within the react team. It was hard and tedious to keep up the fixing bugs from one framework, and transferring that over to the next framework. That got me thinking about how can we potentially view this framework agnostic warehouse of component logic, and just share this across Chakra UI React and Chakra UI Vu. So, this is where the journey started, interestingly. In there I took a deep dive into state machines, charts, learning all this stuff helped me open my mind to the possibilities in there.

JASON: Absolutely. Very cool. Yeah, and David I think is in the chat today. We've had David on the show a couple times. For folks interested in kind of a crash course in state machines, we have a couple back episodes of Learn With Jason you can go and watch. Those will be in the show notes if you want to watch them. So, when you're talking about state machines, you know, you mentioned there's complicated UI, it's hard to share these bugs. And what I've heard when people start looking at state machines is they feel like it adds a lot of extra code. When you look at a state machine, it feels like it's very complex. What has your experience been, do you feel like you're writing more code to manage Zag with state machines? Do you feel it's prevented you from writing additional code? How have you found the experience of maintaining a component library founded by state machines?

SEGUN: It's been a great experience. So far, I've been working on Zag for the past ten months now. It's so much easier compared to maintaining React, because there are a couple of issues with trying to build a custom hook for a component in React. One of the issues could be, like, why not take a look at this hook code, give it a name, USelect. How do I understand how the events and transitions happen within this hook? So, you're going to see a bunch of states, code block, memo, sometimes use context, and the question is how do I wrap my mind around how this component works, basically. If you tell me it works, where do I look exactly to fix it. You then, consider log here, see if it works, here, then get to the point where you try to fix it and it's done. But then that looks to me like you're basically plumbing through your codes, trying to find where the holes are in this pipe, and duct tape that hole there and we're done and move on from there. I found it's a whole lot easier to think about how a component works, and the transitions of the stage the component would have to transition between them. Usually, when there's a bug, I find I'm asking myself much more quality questions. It's not exactly like is this caused by effect, double random issue, where I have to check maybe it's some strict mode issue there and there, but I find myself asking questions what state and transition caused the issue, and how do -- I can easily narrow down where the bug is and fix it instantly. I think the joy of this is the fact that once you do this, it's solved for every framework out there. It's like a piece of logic that powers the component.

JASON: You know, that's something that's kind of stuck with me about state machines, is this idea is this isn't new technology. This isn't framework-specific technology. This is something that's been around for a long time, the idea of describing state with state charts, and kind of the goal behind finite state machines. This is established practice from, what, like the '70s or something. It's been around for a long time. So, if you can write this in any programming language, the logic stays intact across any framework, and that, to me, is really exciting. Like you mentioned, the portability of things, you know, it's very easy to write code as if it will live forever and you never have to maintain it or port it or anything. But anybody who's been in the game for a couple of years, we've all had to port from jQuery, to Backbone, React, Angular, or whatever we were working on. Probably likely in the next five years we'll be doing it again from React to whatever the next thing is, or if we're not using Next or Remix, we'll be porting to the next React framework. There's always something that's going to change. And knowing your application logic is solid and transportable is really, really exciting. The other thing I love about it is this idea that the point of a state machine is it draws all of the complexity of UI into view, as opposed to letting it be hidden. Like you said, if you're not paying attention to this, you have leaky pipes, you have to duct tape something, you're trying to figure where it is, and you write a special handler case, and that's all on you. Whereas with a state machine, you explicitly define what's possible, and the only way that you would need to fix that is if a new state becomes possible. And, so, where do you -- as you've been building out Zag, what are the components where you've seen the most surprising amount of complexity as you've built them out?

SEGUN: Yeah, that's such a good question. I would say I'd pick two. One of them is the menu machine. Specifically, when you have a menu that supports nested menus, basically. This is the possibilities within Zag. We struggled a lot last year when we acemented to do this in classic React. So many hacks in the code, and I got so embarrassed to the point I deprecated, no, I don't think we're going to ship this. I mean, it works, it looks pretty stable, the tests are passing, but I don't feel confident in this code. And if anyone opens an issue with this component, I'll most likely start to cringe. I don't know where to go. Such a huge mess. What I'm trying to do now is be very, very careful how the machines are modelled. Very predictable, you know what's going to happen, you know what state, what transition, what event caused this bug to happen, and you can narrow it down and fix it. The menu and adding submenu, menu support, was quite a challenge, because trying to get that interaction to work, because you have one menu and another menu. Each menu is its own piece of logic, but then you need to figure out how do you interconnect them together and make them linked. That's a huge challenge there. The ideas are pretty complex initially, but I think I'm starting to get the hang of that right now understanding the premise behind state machines to be a complex logic like that.

JASON: Yeah. I find that to be, you know, and it's also interesting when you start thinking about even the really simple stuff like a text input. There's logic that you just kind of take for granted, right. There's the inactive state, the disabled state, the selected state, selected or focused, and in each of those states, things should and shouldn't be possible. Like whether or not you can put text into the input, but because if you're not using something like a state machine, which explicitly enables or disables that feature, you have to just assume that no one would ever try to update the content of an input without having selected it first, and, you know, hope for the best, right? And to me that's such a wild way to look at it, because you'd think, oh, it's an input. Inputs are whatever, leave them alone. They are so -- every bit of UI is complex. I think there was a slide at React-a-thon, where someone was showing the number of implicit state that is exist with even relatively simple components. And, you know, it's factorial levels of complexity, where it's like, oh, tens of thousands of ways that this could potentially fit together, unless you get really descriptive about this state allows these two things. If it's not in this state, it can't do those things. And if you try to do those things, it will break. And now you've been explicit, so your components do exactly what you expected. So, how many components have you been able to put into Zag so far?

So far we have close to 12, 12 components. I think probably 15, if I'm not mistaken, but then just a few of them are documented, and ten of them are currently documented live in the docs for reasons just like sometimes you mean to document the thing and it becomes a huge task. I want to be able to show people use cases and examples for this.

JASON: Michael Chan was the speaker who talked about all the implicit states. Michael has also been on the show. So, I'll drop a link to his talk there, for anybody who wants to see more from Michael. He's brilliant. You should watch that. So, at this point, okay, I feel like I could ask you about a million questions here, but it might be more fun if we can just take a look at the code and show how it all fits together. So, let me switch us into our paired programming mode.

SEGUN: Okay.

JASON: Here we go, all right. And, now, let me get this off the screen, so that we don't get the screen inception there. And let's start by talking about our captioning. So, quick break here. We've got Ashly with us from White Coat Captioning taking down everything we're saying today. Thank you very much, Ashly. And that's made possible through the support of our sponsors, Netlify, Nx, and Backlight, all kicking in to make this show more accessible to more people. And we are talking to Sage. So, if you want to follow Sage, head over to Twitter and click the follow button. Smash that follow button, ring that bell, all those good things. Let's see, we talked about there's Michael Chan, there's that smart fella. And then we've got David, who's with us in the chat today talking about state machines. And we are looking at Zag.js, which is right here. And that is about as much as I know about Zag. So, Segun, what should I do first if we want to start building a project?

SEGUN: Sure, I mean, just take a look at the homepage, let's scroll down a bit to get a fresh idea of what Zag is. And one of the key premise of Zag is that we -- the machines know the logic with you, powered by state charts. One of the key things we try to do is build in accessibility as much as possible. So, this is basically like a sum of all my experience building Chakra UI, my heart and soul going into figuring out even though these are state machines, how do I make sure people view accessible experiences using these machines. That brings us to an interesting topic. But, overall, we try to keep it as framework agnostic as possible and provide adaptors for different frameworks. So, you can scroll down, you see there's an interesting example of the different frameworks down there. If you go one more. So, right here, you see an example of what Zag looks like for three key frameworks, React, Vu, Solid.js, where we tested Zag and make sure it works well. So far, we're proud of its outcome.

JASON: Oh, wow.

SEGUN: This is a setup of Zag and how simple it is to work with.

JASON: Wow, this is great, right. So, we've got the number input, use machine and use setup, set it up to have the state and ascend, which is a state machine kind of send an action. Then we set up a ref for our container.

SEGUN: Typically, what that is from my experience in working with Chakra UI, you have to take into consideration the fact these components should be able to work within an I. Frame or custom window environment. This setup does two things. One of them is help you provide a quick ID for that component, which every component on the page needs to have some ID. And second one is provide the correct reference to the owner document. So, owner document is basically a reference to the correct document. So, if you do things like document.query selector, those things if you're within an I frame, the reference document is not the same as the global document that we're used to. The setup takes care of all those nonsense, basically.

JASON: Very cool, okay. If we look at the implementation here, too, in React, right, we're using plain-old HTML and spreading out our API to increment. And our input props, okay. All good. And then we go to Vue, and it's the same thing. We come down here, we have our ref, we spread out the decrement and increment in our input. It's just plain-old HTML. All right. Let's go look at Solid. Okay, input, props, machine setup, makes sense. Here's use machine, and we end up down here again with plain-old HTML, decrement, increment. So, this is really powerful. With the exception of this logic around the API, which it looks different in Solid. Vue has a little bit of a computed thing. And then the API and React uses this .connect. They all connect, but they have helpers it looks like. This is incredible. What I was honestly expecting, and this is me with no context, what I thought you'd see is like Chakra and other libraries where you have import number input and that's the whole thing. I didn't expect it would give you the logic prepackaged. This is great.

SEGUN: One of the key premise here is we want to provide headless components. So, the idea, the premise behind headless components is you provide the logic and the event handlers and properties. And then you just let the user take control of the UI, style it however they want to actually do. Because I think this provides a lot more value to people developing, whereas if you give them a prepackaged component library. And I think the way we build Zag, we'd like it to be the heart and soul of most component libraries out there. Basically, one level lower than Chakra UI, but help provide a sense of logic and standards across all frameworks, basically.

JASON: This is interesting. You're not actually saying go out and as a typical consumer, use this to build your consumer UIs. You're almost saying, hey, component library builders, use Zag to build your component libraries. That way you get all the cross functional support more or less baked in.

SEGUN: Yes, exactly. I feel like I think this serves right for both component libraries and application builders. If you wanted in your app to have this number input component, you don't have to go install some component library, because there's a high chance that you have a design in mind. Just needed logic for this. So, you can basically grab the logic and design it too much. And that's it.

JASON: There's a concept I hear talked about a lot, the idea of progressive disclosure. I talk about it as the progressive disclosure of complexity when you're talking about apps, where, you know, what I want as a developer is to do as little coding as possible until I need to do something custom. I want no boilerplate, I don't want to have to write a bunch of stuff, copy/paste a bunch of stuff, writing code that isn't really unique to my app. I want everything to sit as there's your app, you know, this is one of the reasons I love services like Netlify. We take away this whole layer of complexity about getting things online and serving and posting. Let me break out just to the layer I need to modify. I get that, I don't have to write a bunch of counterlogic or things like that to make it function. But if I needed to, I have escape hatches to modify it a bit, change things out, and start making it my own. The important thing, I don't have to.

SEGUN: Exactly, you don't have to.

JASON: Now, I love this. I think this is great. So, all right. So, I want to give this a try. And I know React better than most. So, that's probably the right place for me to start. Would it make sense for me to get a code pen to start, or is there an easier way?

SEGUN: Yeah, either set up a code sandbox, or use local VSCode. That works, as well.

JASON: All right. Local VSCode. I will make a new directory. Call this Zag-state-machine-components. I'll get into this. Did you want me to run?

SEGUN: The code we have to check.

JASON: It is...

SEGUN: Npm create.

JASON: I want to do it in this directory, though. Package name is copy/pasted is what it's going to be. We'll go with React, because that's what I like best. TypeScript or non-TypeScript?

SEGUN: I'm a TypeScript junkie.

JASON: TypeScript it is. I'm going to run npm install, and we'll open this. So, this is our default site. We haven't installed Zag yet. We just have the base of a project. So, it's React, it's TypeScript, and it's vite. That's all we've got right now. We have the ability to run and develop lowly, but I want to include Zag. So, what's my first step here?

SEGUN: Awesome. So, two key things you need to install, depending on the components you want. So, if we head back to the Zag docs, if we can switch back, and maybe just go all the way up. And then click get started. And let's scroll down to any of the components. On the left sidebar, let's pick your number input component. We saw that one just now. So, we scroll down. Let's as the npm for Zag and React.

JASON: All right. We're installing the number input explicitly and Zag.js core for React.

SEGUN: Exactly. The Zag.js number input is just the agnostic input logic. Then you have to install React, which is the React binding for that machine.

JASON: Got it, got it, got it. Okay. And that's interesting. If we look at the number input on GitHub or whatever, we're going to see a state machine. We're not going to see UI logic or components or anything. We're going to see just a definition of state.

SEGUN: Yeah, I'd love to check it out. Let's go to GitHub and see what it looks like.

JASON: Yes, let's do it. I love this. All right, we're going to packages, and then we're going to find machines, then look at the number input.

SEGUN: Let's go into the source, and then open the .machine .ts file. If you scroll down here, you see the state machine configuration or definition of the number input. We have here the context, shared data the number input machine uses, and we also provide a way to actually modify the shared context into. We'll get into that. The number input is something that increments. So, you should be able to set the minimum, maximum, step size, things like that. I'll show you how to do that shortly. Then we have computer values. We've been able to compute based off the existing context information that we have. Just build in within the machine itself. And then it goes on and on to show the different states, the transitions, different transitions between the states, events you can send, actions, and so on. Basically, everything within the number input component. And this is what I mean, whereas if you find any issue, track down to what state, what event, and then just fix that transition instead, or you might even be script logic. But what you see here feels less overwhelming than if you had to write this in classic React. It's hard to track. Even if you put it into a reducer hook, React would bite you in the ass if you had to do this. We currently do this within Chakra UI, but that's the reason we created Zag, to make it a while lot easier to manage.

JASON: Right. And you can see, this is, you know, I would not have expected all of the things that we need to be able to do. And, you know, we're seeing a bunch of guards in here and things that we need to make sure are true. So, a lot of stuff to check. You can see the guards here, tied to actions. There's just a lot that happens, and when you're looking at something like this, oh, yeah, it's -- I would implement that in React with three lines of code and be really surprised when it broke. Well, how can this possibly break? It just goes up and down, this is easy. Then you immediately find ways that it could break, get into weird states, somebody finds a way to mess with it, and having a state machine allows us to solve that problem, you know, meaningfully. And, in what I would say is a more predictable way. Because I've seen the code that I write when I start to just patch, and patch, and patch every time I find an error. It's not good code. Nobody wants to read that. We peeked under the hood, and we have ourselves a basic React site with vite and TypeScript, and we've installed Zag's number input. So, if I want to get rolling, I'm going to hop into this and empty it out and let's get weird in here, let's make something.

SEGUN: Sure. So, let's head over to the number input docs and grab the basic example in there so, we can actually see something working pretty well.

JASON: Make sure I didn't break the site when I just ran this. All right, we have a vite site running and it is making this render. Okay, now I'm going back to the docs. And we've got installation. All right. Usage. This seems like what I want. So, I'm going to start by importing everything as number input. I can do that.

SEGUN: If you scroll down, you can see a full snippet, you can copy and paste that into the project. I'll take my time to sort of explain what each vite does in there.

JASON: Yeah, okay. So, the first thing that we're going to do is we're going to grab the pieces that we need. So, when we use machine, we're getting number input.machine, and that is what we found here.

SEGUN: Yes. Basically, that describes the entire logic of the number input. This gives you access to the state of the machine and a function to send events to the machine. So, this is one layer. What we tend to do next after this is for people to effectively use this machine, you basically maybe need to have an idea of the transitions going on and the state within the machine, and it gets really tedious. How do I help the consumers of Zag know about the different state transitions, do they have to see what they can actually do with the machine? So, to solve this problem, that's why we created an extra layer in line seven called connect. What connect basically says is give me the state, I'm going to cook everything for you. Basically going to cook your meal for you and give you everything that you need to build the UI pieces that you need. Inside this connect, we abstract or consume all the stuff coming from the machine and give you an API that you can work with. And what's within this API are two key things. One of them are sort of props that you can basically spread on to the components. And the second one is state properties. Things like disabled, things that you classically have to set up a use state for in React. Basically, everything in there, setting your value, clearing the value. So, more like the HTML API for a number input. That's what you're trying to provide here.

JASON: This is great. You know, TypeScript, right, we can see what's available, and I can increment or decrement, we can focus, and then we have the props you described. We can check things. Is it focused, is it invalid, and that will let us do other things. We can maximize, minimize, set it to an arbitrary value, oh, and value is number. That's handy for saving yourself from accidentally falsely comparisons of if zero equals false kind of thing, those types of problems that will always get us.

SEGUN: Exactly.

JASON: This is great. So, I have my ref and my API. That means the next thing I want to do is set up -- this gets us started. The next thing we want to do here is basically take a look at what I call the anatomy of the component. To get a number input to work correctly, what parts do you need? Number input typically includes the input itself, a label at the top for form fields in case you need to use that within a form. Needs to have an accessible label. Some button to increment or decrement the value. This is typically the triggers to sort of change the value. And I think even the classic type equals range or number provides by default. But this is not customizable. What you've done is help see the anatomy of the component, so you can actually view that according to the anatomy. And this is very crucial to understand and use the system. We know we're going to have a label up there somewhere, and we're going to have the input.

JASON: Going to have a div, and that's going to have an input. Increment and decrement. So, we have a button to decrement and button to increment. Now we have to make the buttons and labels and everything do stuff.

SEGUN: Exactly. One of the things I have to mention is this is not tied to any HTML markup. So, don't even have to wrap them all within a div. Based on your UI requirements, you can move the components around, so that is not exactly tied to the UI. It's just for the example on the demo page and website that we have this structure here. So, we can decide to switch up the UI to something that has the increment and decrement, but to the left and right of the input. Maybe work with that UI instead.

JASON: Yeah, okay. Let's play. Do something a little different from before. We have these bits here. And I need to get some of this API stuff going on. So, I'm going to with my decrement spread out the API.decrement button props. Going to rely on TypeScript to save me here. Then I'm going to spread out the increment button problems, right:and then do I get input? Nope. Wait, expanded something for me. Input props. There they are. All right. Then I've got my labels. So, let's go with label props. Oh, it's good, it's good. So, we'll say a check here. What don't you like? Form elements must have labels.

SEGUN: I think we can disable that. Zag ensures that the label actually works correctly.

JASON: Okay, I don't need to set my --

SEGUN: You don't need to set the ID up there at least. Maybe we can disable this and figure out --

JASON: Right? Nope. I'm really bad at TypeScript.

SEGUN: I'm not sure what the issue is here.

JASON: It will be fine. We'll just ignore it. Okay, it's yelling at me, but yelling at me for things unrelated to what we're doing, and that's -- we need root props. Okay, root props and we're off to the races. API.root. If I save this, I'm pretty sure we're still running. We are. I'm going to come back out to our home page here. And we've got already a functioning thing.

SEGUN: Exactly. Not any work, just in there. It's ready for you to style this to suit your UI. If you have design system or styling solution, you can just go in here and style them. I think one thing I want to focus on before we start writing the CSS, if you inspect the element, something we need to showcase before we start styling. Based on the anatomy I described early on, every component has an attribute data part, which is a way to identify that element. Based on the anatomy that I described earlier, see it has a part called label, buttons are spin buttons. That's, literally, what the docs call them and what they are called using the browser. The input is called the input. So, you, literally, sometimes if you're writing CSS, you can go directly and use the name of the part. So, this is heavily inspired by web components and part selector components. Basically, given the name, and you can go in there and use that to start the components.

JASON: Oh, slick. If I come in here, instead of having to add class names out here, what I can do instead is data part label and I can do something like display block. Now I need to import this again so we are actually using it. Come back out here. All right, that's working. Let's go side by side here and do the fast version of CSS. All right, chat, I know I've said this before, but this time I'm serious, I'm really going to be fast on this. So, we've got like a data part spin button, and then in here we can do something like, you know, background, black, and color white. Yeah, yeah, I love this. It lets us do useful things. One of these, one of those. A data part input. And then we'll say margin. Unbelievably, this is slick, because I understand what's going on, as well. What I probably do in here is set up something like, you know, a component number picker, right, and you have the number picker component, maybe that's a separate CSS file, maybe it's organized inside of your code. That sort of work, I think, makes it really straightforward and kind of nice for us to understand what's going on without having to do a bunch of customization. Yeah, I love that I didn't have to get in here and do a bunch of class names. That would have been a huge pain. This is nice and portable. I like that.

SEGUN: Awesome. Yeah, so, now that we styled the elements, let's see how can we customize the machine, I mean, to feed different criterias. So, let's say you wanted to maybe set up a minimum and maximum value for these machines. So, what you can do is if you head back into -- let's wait for this one.

JASON: Sorry.

SEGUN: That's okay.

JASON: Leave me alone, chat, I didn't mean it. I did this one thing. There, okay, that was going to bother me otherwise. Now back in here.

SEGUN: Say I wanted to customize this machine to have a minimum and maximum value, custom. Basically, what you do is instead of just typing the machine, you call the machine as a function and just pass the object in there that includes min and max. So, let's set the min to whatever values you want. But there's a whole lot of options in there you can use to customize the behavior of this machine.

JASON: We can see I'm able to use the mouse wheel, overflow, clamp value on blur, which I assume means if I were to put a max of 25 and then I type in 100, and then I unfocus and go back down to 25.

SEGUN: Exactly, exactly.

JASON: We can disable it, focus change. All sorts of cool things in here. Oh, and we've got handlers. On change I can fire extra logic.

SEGUN: Exactly.

JASON: Let's do the easy thing. Busted. I have a very basic set of logic improvements here, and I'm going to refresh the page and go to -- so, we started at five, good. Try to go over.

SEGUN: I think you can also press and hold down mouse instead of clicking one time.

JASON: I tried to delete the value. Can't do that. It's busted. I love this. This is great. Let's go with, you know, five is fine. 50, though, can't type 50. This is good. It clamped back down. This is cool, this is really good.

SEGUN: Yeah, exactly. This gives you an idea how to customize the machine. I can easily imagine a scenario where you map this into props and pass the props down to the machine and it works easily. If you wanted to do the component library of this. Another thing you could do here, let's change the step size. Instead of having it increment by one, let's do a much higher step size and see what happens.

JASON: So, step of ten. Yeah, that's great. Then if we want to set the initial value. Start it at min. Doesn't like that. Number is not assignable to string. There we go. Now when we refresh, it starts at the minimum. And what I love about this, you all might have noticed, I didn't have to ask about that. I was able to ask the code. I could just go in here and start messing around with what my auto-complete says. Get to something that makes sense to me. Really, really nice.

SEGUN: One other thing, if you reach the min or the max, you cannot click the button anymore. And we upend extra data attributes to this component. So, when you reach the max or the min, right now if you check the increment button, because we're already at the max, see the disabled property attached to that element, so you can go into the CSS with the data disabled attribute and add styles to that to maybe change opacity or color.

JASON: Now I can do something like data spin button, and we'll just take all of this -- and how do you do -- there's a way to do --

SEGUN: Square brackets.

JASON: Like that?

SEGUN: Yes, exactly.

JASON: Oh, nice. I don't know if I've ever done this selector before. So, I'd be able to do a background of, let's say -- no, change to opacity. That's an easier. So, when it's disabled, semi-opaque. Now I know I can't click it.

SEGUN: Exactly.

JASON: Slick, really nice. How much noise are we getting from my neighbor with the weedwhacker right now? Okay, we've got the ability here to really, astonishingly quickly, build a number input that has all the states that we want. I'm not going to lie, it always feels great when something works this smoothly. We have very little logic in place. I've written four CSS selectors and a grand total of 30 lines of code, and we are at a really nice working number picker. That's really, really, really nice. Cool. So, what should we try next?

SEGUN: Yeah, for sure, we can try different things. Let's say we want to send events. If you wanted to build this up into a component, let's see how we can do that. Maybe we encapsulate this into a number input component, pass some props to that, and send it down to the machine.

JASON: All right. I'm going to copy these props. And then we're going to take all of this, copy/paste. And what I'm going to do is take this and get rid of all of these bits. Instead, up our number picker, min of let's go with 1, and a max of 100. And I'm going to take out my main. And this bit.

SEGUN: Exactly.

JASON: So, now we have a component, and just to make sure that's working. This is our encapsulated component here. And if I move this around, we'll kind of see it all moving, right. So, now if I go -- now it's at an increment of 1, and the max is 100. Okay, so, it's respecting our props. Now we can do some stuff.

SEGUN: Exactly. Let's accept line 6, default value or initial value, so it actually communicates the intent of that prop in there. And then let's just pass on an unchanged handler. This would send it straight into the machine, and when the value changes, we get notified with this callback.

JASON: Okay, we have an unchange, we have an initial value. I will set an initial value, just leave that, I guess.

SEGUN: Yeah, we can set a default or just leave it, it's fine.

JASON: Should I do something like this to make sure that it works?

SEGUN: That's okay, that's okay. Yeah, awesome.

JASON: Then in here I'm going to do an initial value of we'll say ten, and we wanted an unchanged, and for now, console log something and say -- -- open this up, and any time I make a change, we get what up, chat. All right. This is pretty understandable stuff. I don't feel like I'm stretching my brain to understand how this all works and fits together, which is always a good feeling when I'm working with something brand-new.

SEGUN: Exactly.

JASON: I'm building a component library. If you told me today I was going to build a component library, I wouldn't have thought I would be this far in, what, 40 minutes. It's very -- this is the kind of thing that makes me feel really empowered as a developer, is when I don't have to spend a day and a half setting up the boilerplate or the foundation code to build the thing that I'm supposed to build. I want to get right into building the thing that's valuable, which in this case is a piece of UI that looks the way that I want it to, and I know isn't going to break. I didn't have to build the logic or underpinnings. I just had to make this look the way that I want, and now I have a dropable component that is configureable in the pieces I want it to be configureable on. And the rest I can control internally. Step size, I can control that and say you don't get to change my step size. It's always 10. Or I can make that a component. But now I have control without having to write that logic myself. I get to, boop, there it is. This is great, I like this.

SEGUN: Awesome. Two things. Scroll down a little bit. So, in the unchanged, every event handler in Zag gives you details of that event that happens. So, you can grab details from that unchanged callback in there. Let's log it out to see what comes out of that when it changes.

JASON: Okay, I've got my details. Let me refresh the page here. All right. So, the details that we got are a value of what the new value s and the value is a number of 20.

SEGUN: Exactly. You can go over this and use it to fire up the UI or send this to some API. It works automatically, even with found data, for example. If you wanted this to work within a form, all you have to do is have a name attribute to the machine. You can now submit that within the form.

JASON: Yeah, so, check this out. I'm going to do a little bit of a thing here. If I take my current boop count, now I'm using React state. I'm going to pass React state in here, and then I can set boop count to details, value as number. Right?

SEGUN: Yeah, using TypeScript. We did not define a type.

JASON: See if it actually fails. Sure doesn't. Lets me do whatever I want. Now look. I have this. And if I want to use this somewhere else, so with current boop count. It works.

SEGUN: Exactly.

JASON: All I had to write was the important logic, the stuff that I care about. Not the things that just make a component function and make sure that we don't break it, but what I care about in this app is when somebody sets that number to 10, it shows 10 elsewhere in the UI. Did that make sense, got it, great. Yeah, this is really, really, really nice.

SEGUN: Awesome, yeah. For the TypeScript-conscious folks, let's see if we can tighten up the types a bit more. Scroll up all the way to line 7. We can sort of type that prompt in there. Or maybe let's create a type called number pick of props and actually define that.

SEGUN: So, props here, we know the main is a number. That goes in there. We know the max is a number. That goes in there. Initial value is also a number. And then unchanged. How do we get unchanged? Every machine, so you can do number input.context, which is number input as in line one. Number input that context. Then you can grab the unchanged from that context, basically. So, do square brackets and grab unchanged as a string in there. Yeah, that way you get a type, exactly.

JASON: Oh.

SEGUN: If you pass it on to the component, which I don't think we have just yet. Yeah, line 14. Let's upend the type to that component.

JASON: Number picker props.

SEGUN: I think it's to the left of the braces. Just after the structure and you put the column in there.

JASON: Right, right. That's not -- there we go. Now that's where it needs to be. And if we go to these details, it actually shows us the value, which is wonderful, because I actually had to type that out. But now if I go in and type a dot, I press enter all the way to victory.

SEGUN: Exactly, awesome. The last one I can show you here is basically more stuff you can do with API. So, if you come in here to the number input component, let's create a button within the same number. If you want to show a number picker, add one more button that says clear.

JASON: It works.

SEGUN: Let's see, how can we make this component work. We don't have an API.clear button, because that's not part of the number input. But we do have a function, where you can call if you wanted to. So, let's do an unclick here, and within the callback in there, we can call API.clear. Clear value, I think.

JASON: So, API clear value. To make this work, I can do data part --

SEGUN: Exactly.

JASON: Now I've kind of followed my convention coming out of Zag. When I come into my CSS, we can do one of these and say let's go with a border none, and background none. And we'll do a margin left of -- and, you know, whatever. This is not -- we probably have to talk about how this all works to make sure it's making sense for people. Yeah, we have our on invalid. That has to work. Let's get rid of that. Okay, that's gone. Now I increment, clear. Wait, I got to reload. You would like zero boops. Oh, it comes down to zero, that's nice. I love it. I think this is great.

SEGUN: Exactly. So, this is the true power of the API. It gives you some key functions that you can use to sort of modify the logic of the number input. Most of the API, if you explore that and see all the functions exposed to you, I think functions like set value to min, set value to max, and so on. So, you can use the functions to actually enhance the experience of the number input. Awesome.

JASON: Yeah.

SEGUN: So, just to make it -- go ahead.

JASON: I was just going to say, I'm just kind of revelling in how nice this is to work with. And how, you know, the level of potential that I see in this sort of almost like what's the next layer of abstraction below a component library, right. This isn't something like bootstrap or material UI, where what you're getting is making an UI that looks like this. What you're getting instead is prepackaged, battle-tested logic that you can rely on, and you get to make it look like whatever you want. So, to me, this is the first time I'm seeing an UI library, where I'm kind of excited to use it. And that's no shade against anybody who's building component libraries. I just like to make things look the way that I like, and that usually means I end up fighting with component libraries more than I care to. So, I, you know, for my personal projects, I'm always like I'll just design it myself, because it's easier for me than learning a component library styles and then reverse engineering them so I can override. This, though, I don't see myself wanting to override the way that an increment and decrement button works. Or the open and close states for a menu, or things like a select -- I don't want to build that. Typically, what I do, is I don't build that stuff, and I just have text inputs and lots of instructions. Don't do it like this, or it will break. Because I don't want to build that logic. So, looking at this, I'm actually excited, because I can see using this and not feel I'm giving up control over the way my app looks. Just being confident that it's going to work.

SEGUN: Exactly, exactly. This is like the key premise behind Zag. We want to provide you the key pieces you need to build your app. Styling out of the way. Just like the piece of logic. Key premise, we don't want you to sweat over the logic, how is this going to work. We don't want you to sweat over how do I attach an unclick or mouse down or pointer down. What we do is take the proactive approach to wrap this up on to an API and you don't have to worry about anything. Show this works fantastically well within a form. So, you don't need control states to send data through a form. All you need to do is add a name to the machine, the machine knows how to figure it out, and it just works out of the box.

JASON: Very, very cool stuff. Knowing we have roughly 15 minutes, anything else you want to show, or is this a good stopping point to go and explore?

SEGUN: Something that I love to show. I know someone asked me, is there a way to visualize the logic of these machines, just like the X state. So, we've been trying to figure out a way to do this. The syntax within Zag is different, but still also pretty close. What you're going to do, Abraham, one of the guys that work with me to improve Zag. I'm going to put in the chat for what we're working on, work in progress. Probably a few bugs in there, but this gives us a link to all the machines we currently have in Zag. If you click the number input, for example, it helps you to see the level of complexity going on in the number in the machine itself.

JASON: Whoa.

SEGUN: So, you can take a look at all of this, see what's going on internally. If you don't want to use Zag, you can go ahead and use your own. Just use this as the guide and the map to actually give you some direction. And even when you find bugs within the machine, you can easily launch this, look at the state transition, and be like, okay, I think there's a bug in here. Maybe we need to fix it. This transition is not right, or this guard is not right. And it makes it easier to find issues and fix them. So, actually, what I'm trying to do within Zag is any time anyone opens a PR to fix an issue, you want to see the before and after visualization like this. So, if you open the PR, we want to see what the logic looks like before, what it looks like after, and be able to make decisions based on that. So, I'm not looking at how fancy the logic or JavaScript code looks like. I'm looking at the logic and state transitions. Do they actually work or do they introduce new bugs.

JASON: You know, what I love about this is it simplifies things like code review. And, I mean, when you start talking about state charts, they simplify code review in a big way anyways, because they just -- there's a pretty true or false success state in here. But when you get into visualization, the conversation changes, because now, and when David was on the show, we talked about this a lot. When I have something visual like this, I can go to the leadership team, designers, product team, and I can say this is what it's going to do. Does that line up with what you said it should do. And you can have a conversation about a chart like this in a way that you can't have a conversation about code. Now, this is still -- there's a lot going on here, but we can walk through this. I can follow this through what the app does, you know, we get to -- all right, there's the ability to press down. You can do all this different interesting stuff that lets me control, increment, decrement, we're going to do different stuff. And based on what somebody has done, we have the ability to talk through the logic together. That, to me, turns code into a communication tool in a way that it always was implicitly, but it's not explicitly unless everybody in the room can code. With visualizations, now it's explicitly a communication medium, because the code generates a state chart, and you can talk about a state chart.

SEGUN: Exactly, exactly. So, this is pretty much like the premise. And I think that this is such a game changer here, because now we shift the conversation from the user face dependency isn't complete, or you need something in here, a top-down approach, where you think about how the component works first. You model that in a machine, or if you wanted to fix a bug in here, you can easily see at what point. You can actually detect the point of failure. And we can then open the PR, we can review what it looks like before and after, and be like, oh, you're actually right, we missed this transition, let's add that into the machine. And it just works.

JASON: Yeah. Yeah. And it's pretty easy to see if you've got a state that's just sitting over here with no lines connected to it, you know you forgot to implement that. That's one of my favorite things about state machines. If you go through and start thinking about it, you can say, all right, I'm going to build a log-in screen, and that needs -- there's the log-in screen, there's when I've actually started filling it out, and then there's the it's been submitted, it's submitted successfully, it's submitted with an error, you know, the different error states for the inputs. And you know when you start talking about design, we've all been in that situation, where we talk to a designer, yeah, give us a log-in form, and we realize halfway through coding it that we're missing a bunch of failure states or edge cases. But if we start by working with state chart, we get a checklist. Yeah, we need a failure state for user name, for email, for whether or not the form submission worked, failure state for incorrect password, not found email. Any of those things is going to be something that you have to handle. And then you can have a conversation about how to handle those, and the designer knows that it's expected, not like, oh, yeah, here's an input form, go code that. You know what the states are and have a better conversation and much more clarity about scope.

SEGUN: Exactly. So, yes, that's one of the key setting points of being able to visualize this logic. So, I'd like to show you the final piece, which is a secret feature within the number input.

JASON: Secret features.

SEGUN: Swipe back into the code. One second, maybe you can just stay on this screen in the browser.

JASON: Okay.

SEGUN: In here within the state chart, you can see an event called pointer down scroll bar, pointer move scroll bar. That gives us a hint to tell us we can scroll up the values of this number input. As opposed to clicking some increment or decrement button. If I wanted to be a design tool, I want people to scrub through the values really fast, as opposed to clicking up and down, plus and minus, how can I implement this. Let's then go into number input and activate this hidden feature, basically.

JASON: All right, let's do it.

SEGUN: So, what you do is add another chart element in there from line 25. Add another, like div component or element in there. And then give it spread on the scrub props to that. So, what you do here is you go into the CSS. And let's just create a style inbox, basically. So, scrubber. Make it a scrubber for now.

JASON: Okay, we'll go border, two picks, solid red. Both a width of 25 and a height of 25. That should give us box. There's our box.

SEGUN: Yeah.

JASON: Oh! Really straightforward. Yeah, this is great.

SEGUN: If you do a large max value, you may see one of the benefits. If you want to scrub through 5 to 1,000 or 2,000, you see it's a whole easier to use a scrubber. Gives you that affordance that you need to scrub through the values really fast. Hidden gem in there --

JASON: Let me refresh. This would take me forever.

SEGUN: Potentially just mouse down. Potentially mouse down on that one. Increment is really fast.

JASON: Just holding mouse down still gives you a while.

SEGUN: Interesting thing, it wraps. In there, it keeps going, keeps scrubbing the value in there. I think there's an API that locks the pointer. I think maybe using Microsoft Edge, but if you use Chrome, for example, you'll probably be able to get that wrapping feature out of the box. Or maybe Firefox, if you have Firefox, that should work, as well.

JASON: I uninstall all my browsers? I might have.

SEGUN: That's okay. We see we're actually able to scrub through the values using the pointer. That's an extra effort. Imagine the scenario where you can wrap the input with this scrubber, and a lot of people scrub the values, as opposed to manually having to use increment or decrement. This is popular in design tools or actual web app tools. So, this is also built into the machine. This is what we mean, gives you everything you need, effectively.

JASON: Really, really slick stuff that, you know, there's a lot of very good potential here. So, let's set this to a slightly less absurd value, and then we'll refresh the page. There we go, now we can actually scrub. I tried to go absurd to see what would happen, but as usual, moderation is key. So, now I can scroll back and forth. Yeah, I love this. I think this is great. We have our step sizes, everything is doing what we expect. Sage, this is wonderful. Like, really, really love the way that you've put this together. It's super exciting. And, like every episode that we do about state machines, I kind of walk away going, man, I should do more state machines. Don't know why I don't. But you should, chat. Go write those state machines. Your life is going to get so much easier. So, I think this feels like the right time to start asking what resources should we share with the chat if they want to go and learn more about Zag?

SEGUN: Yeah, so, right now the docs is the best place to go. And if you're feeling like you want to know what's going on under the hood, you can go into the Repo, check out the machines we have. And, quite frankly, these are pure state machine ideas. If you look at Zag and you say, oh, I don't want to use Zag on my project, you can install XState. Grab the machine and use that, as well. This serves as a recipe, in as much as it's a library, it also serves as a recipe just for you to see how you can build a number of inputs. Even if you never wanted to use Zag, you can use XState. APIs are pretty close, as well.

JASON: Great. There is a question in the chat from fullstacking. Are there plans for more components?

SEGUN: Yeah, for sure. We have a few components here that don't exist in the docs. Counter box, rating, couple of them in there. Not out there yet. Maybe just focusing on documenting them, getting them out. And then we can go into more solid component. I mentioned typical classic React, I don't even want to go there. A lot of edge cases, but now more comfortable for complex components. Breath of fresh air to know that once this is solved, it's basically solved for every framework and anyone can use this.

JASON: That is really nice. And then Simonflk is asking is React native on the road map?

SEGUN: Not in the near term, but it's pretty easy to put this logic in. There's just a few pieces of dumb things in the machine themselves, and I assume that you can easily write a way to actually make it platform agnostic, as well. But in the core, to see how can we deliver as much value to the web platform first, and then we can go into innate platform. I think the API is a lot less -- a lot more friendlier and easier to wrap your head around, compared to dealing with so many events and git element, things you have to do on the web. So, let's get through the web. A few more components in there, and I think we can maybe start thinking about how we can support React native, as well.

JASON: All right. Well, that is all the time we have today. So, if you have follow-up questions, make sure you go and follow Sage on Twitter. You can find him there. And make sure that you go and check out the sponsors for the show. We've had Ashly here from White Coat Captioning. You can find a link to that on the homepage of the site. Thank you, Ashly, for being here. And that's made possible through support from Netlify, Nx, and Backlight, all of whom are kicking in to make this show more accessible to more people. We've got some good stuff coming up this week. So, we've had Sage here today, and going to hear from Hirday Gupta about building a custom dash. We're going to be checking out Retool. I'm excited to see how this works. Something you watch the demo, that feels too good to be true. We're going to give it a true and learn. May is actually a very busy month. So, we're going to have fewer Learn With Jason episodes, because I'm going to be in person at events for the first time in two years. Remix Conf, Render ATL. If you're going to be there, say hi. Oh, and I'll be at future stack next week. If you're there, come out there, as well. All right, y'all. Sage, any parting words for the chat before we sent them off into the sunset?

SEGUN: No, thank you so much everyone hanging out with us and learning about Zag. Couple fun words around Zag. Anyone that uses Zag.js, you're a Zagre. We call that feeling Zag, so, just wanted to share those. We want to actually live on machines and evolve the web to be like real standard that it truly is. At the end of the day, we want to feel we are engineering a solution as opposed to plumbing our way through to get it to work. So, this is the core premise. I think during all of this, making sure experiences is accessible, I think is the most challenging part of all this. What's the point of using state machines if you can't make it accessible to customers and end users. This is the key challenges in there. Keep it as an open challenge to everyone think about how can you get better building components, building the applications, in a way that I pulled out of the UI, modeling the logic itself. Also taking careful concentration about the accessibility, experiences, and end user experience for your customers.

JASON: Love it. All right, Sage, thank you so much for taking the time to hang out with us today. Chat, as always, thank you for hanging out. Stay tuned, we're going to find somebody to raid. We will see you all next time.

Learn With Jason is made possible by our sponsors: