Create and Visualize a Pin Input State Machine with TypeScript and Zag.js

Segun Adebayo
InstructorSegun Adebayo
Share this video with your friends

Social Share Links

Send Tweet

We've mapped out what a Pin input state machine should look like so now it's time to start implementing that in code.

We'll be using Zag.js to model the machine for us. Zag.js works well with TypeScript so the first thing to do is create types for the machine context and state. Then we'll stub out the states, transitions, and actions for the machine.

While we aren't implementing the actions in this lesson you'll quickly see how a state machine makes complex interactions easier to understand by the events that occur and the actions that are triggered by those events.

Lastly, we'll paste our machine in the stately machine visualizer and you'll see a visual representation of the machine that was created.

Instructor: [0:01] I've gone ahead to extract the pin-input logic into a text-based format. What we want to do is to convert this text-based format into state-machine code. [0:11] I'm going to start by creating a machine.ts file. In this file is where we write most of the logic for the pin-input component. Let's move the README to the side, and just reduce that a little bit. And then, we start writing the code.

[0:26] Zag-js exports a create-machine function we can use to model this component logic. I'm going to create a variable called machine, and invoke the create-machine function from zag-js/core. A couple of properties that this function accepts in the object here, the first one is the id. We're just going to call that pin input.

[0:50] The second one is the state. Then the initial state. Then followed by that is the context. Now to provide the best experience as we define the logic of this component, I would prefer to create a type for two things, for the state and another type for the context.,

[1:11] Now to type the state, I would say the machine state can have a value of idle or focus. Then for the context I would create a type machine context, which, based on our previous sketch for the logic, we have the value for each pin input field, which is going to be a string array, and the focus index, which is going to be a number.

[1:42] Now we're going to insert all of that detail as a type generic into the create machine function. We put machine context and machine state. Immediately we get the feedback on how we need to set up the states. Now the initial state will start from idle. The context value, we need to put in the value.

[2:03] We start out with an empty array and we set the focus index to minus one. Now that we have this set up, we are much ready to start modeling the system. Now on the right here, we have the text based format of the state transition map. The first thing here we see that in the idle state, when the input is focused, we transition to the focus state.

[2:27] Let's see how we interpret that in state machine language. We have the state object here. The first thing we do is, let's swap this over to the top and then we can create the idle state, and then create a configuration for the focus state as well. Now in each of these object, we can specify the on key, typically which represents the events that can be sent to the machine within this state. I'm also going to add the on key to the focus state.

[2:57] Now in the idle state, when the input is focused, we are just going to type the focus event. We say on focus, that's how we read that. We are going to set the target. Target is a property that allows you to change state. We are going to set the target to focus, which means the machine goes from idle to focused when the focus event is sent.

[3:21] That's not all we are going to do. The second thing here is to save the index of the focused input. We come here and we basically define a couple of actions. The key action here is to set the focus index. That transition and state is done. Then we move over to the focus state here. In the focus state, when the input is blurred, we want to transition to the idle state and then reset the focus index.

[3:49] Let's see how we can do that following the same logic. In the on key, we'll add the block key here and we set the target to idle. The action we want to execute there is, it says here to reset the focus index, but what we'll do, we can call this clear focus index. The model there is done. Now, next, it says when a character is pressed, we want to save the value and focus the next input.

[4:21] We'll put in here, we can call this event input, which means on input, what we really want to do, we could set the target, but because we are already in the focus state and there is no state change happening there, we can basically ignore it. We can get rid of the target here, since it's basically the same state we are within, and we can set actions instead.

[4:45] The action we want to do here is to set the value of that focused input. We'll say set focus value. Then the next thing we want to do is focus the next input. Put in here focus next input. That's it, we've modelled the input event. The next thing here is, when the backspace key is pressed, we want to clear the value of the focused input and then we want to move focus to the previous input.

[5:16] We'll come here again and put the backspace key. When that happens, what we want to do is we want to clear focus value and then we want to focus the previous input.

[5:35] The final one is when the text is pasted from the clipboard. That's when I press command C or control C and then I press control V or command V. We want to actually prefill all the values. Here we want to distribute the pasted value into each input field, and then we want to focus the last empty input if needed.

[5:56] We come in here and put the paste event key. All of these keys are keys that I'm making up myself. I'm trying to sort of make them a bit more relatable, so they relate with or they map to the events as well. Now we come in here and the action we want to do here in the paste event is literally to set the pasted value.

[6:19] Then we want to focus the last empty input, if we have a last empty input. There you go. We have all of the modeling of the component DOM and the key states and transitions between them is also modeled here within this create machine function. Now, one of the things I love the most about state machines is, as much as the logic lives here it's super easy to visualize the logic to see what it looks like and get feedback from your colleagues.

[6:52] I'm going to use the StatelyAI Visualizer. What we do is we're just going to collapse all of these different objects, copy it out, and then we switch over to the StatelyAI Visualizer. Within this visualizer, you can call the create machine function that's imported there already, and we can literally paste in the object for our component.

[7:18] Boom, you see that the moment we paste that in we can easily visualize the logic of the component, because everything is set up correctly. You can click around to see the state and the transition between these states. Now, I can take a screenshot of this, share it with my colleagues, get feedback, and possibly also improve this machine logic.