Learn about state modeling with state machines and statecharts can improve the way you develop your React applications, and get a sneak peek of never-before-seen upcoming visual tools that will take state management to the next level.
Hey everyone. My name is David Khourshid. I go DavidKPiano online on Twitter, GitHub, whatever. And I want to talk to you today about the visual future of state management and how X State is going to bring that future vision to reality.
[00:35] Talking about visuals, you probably know where the Ben diagram is. It's a visual and an exact way of representing commonalities between two or more different things. And you might have also used a sequence diagram before to describe how different parts of a system communicate with each other. There's also state machines and state charts, of course, which I've been talking about for a while and state machines and state charts fall under this visually exact diagram category, because diagrams like these are really useful in conveying relationships in a visually unambiguous way. And they each have their own special notations for denoting specific things.
And with state charts, we have the same type of thing. We have arrows and boxes just describing how dates and logic can flow over time. David Harel, who is the inventor of state charts, calls this a visual formalism. And he describes that visual formalisms are diagrammatic and intuitive, mathematically rigorous languages, thus, despite their clear visual appearance, they can complete with a syntax that determines what's allowed and the semantics that determine what the allowed things can be. And I recommend reading his paper on visual formalisms for more information on this really, really interesting idea of basically these diagrams that are mathematically rigorous and also executable.
[02:16] The way that we typically code application logic, whether it's in React or anything else doesn't really lend itself to a visual formalism or to anything really. We tend to collocate data and logic close to the source where it's used such as in event handlers, or sometimes in custom
React Hooks if we want a little bit more organization. While this may be convenience to code, the problem one is that the logic is hard to understand, especially as it changes over time due to events or anything that can happen within the app. And the problem is you can't discern what can happen or how an application can respond to any event or signal at any point in time. And that connection logic resides in the head of the developer who added the logic, which isn't really useful. And you end up with things such as ad hoc logic as well, which obviously should be dried up. But the problem is that when you add that ad hoc logic, the logic, when you dry it up, you might put it in a function or something, and that function may end up itself being ad hoc logic, just living somewhere else. So you're still not centralized seeing everything which definitely becomes a bit of a problem.
[03:44] And enter the reducer, popularized Flux and state management libraries like Redux, reducers provide a way to contain this logic in a centralized convenient location. One hugely important beneficial constraint of reducers is that it forces you to interact with the logic sending events or actions as they're called React and Redux side.
By the way, the naming of actions was a mistake, at least in my opinion. So we're going to be using the term events in presentation, but here's why dispatching events is actually a really good thing. It forces you to reify what can happen at your app in any given point in time. The user may click a button, a fetch may resolve or reject, a timer might go off. All of those are events. And thinking about your app in terms of state and events really simplifies the mental model, at least in my opinion.
[04:46] However, this isn't easily visualized either. Reducers typically contain switch statements, or if statements to discern what should happen when an event is received, thus distinguishing how the behavior of your app can change, becomes a lot more difficult. It mixes those switch statements and those if statements, and so you have to piece together the logic navigating through a bunch of these statements and defensive logic just to discern what the behavior of your app can be at any given point in time, it's all in a single function. And it's hard to pull that apart.
[05:25] State machines are like reducers and they can even be written as reducers, but instead of mixing all the logic together, it cleanly separates behaviors into what are known as finite states. A finite state represents a behavior, which is what the current state is of some actor and how it could respond to events. It might respond to an event performing an action or changing its behavior or anything like that. And that's represented these transition arrows that go from state to states or an event might not be handled in which case the default behavior is to ignore that event. In reducers, this often requires a lot of defensive code, but with state machines it's spilled right into the mathematical model. And more practically this separation prevents impossible states, which guarantee that two behaviors can't occur at the same time and impossible transitions since all transitions need to be explicit and you can't have any implicit transitions.
[06:35] State charts take this idea of a visual formalism, one step further introducing hierarchy. Although state machines provide a way to cleanly organized logic, they suffer from combinatorial explosion of states and transitions, especially when different finite states are actually related. By extending the notion of state machines to be a hierarchical graph, or a high graph as David Harel calls it, we can group states together and represent common transitions cleanly. We can also isolate logic so that we can see the bigger picture instead of having to understand all of the little implementation details in one big flat structure.
Like state machine state charts are also mathematically rigorous, but they can express a lot more complex logic than a state machine can. And it solves the problem of combinatorial explosion within state machines, because it allows you to group related behaviors together. That's a state chart.
[07:39] And this is why I created X State a few years ago. I wanted developers to be able to represent state machines and state chart in a way that can be coded cleanly and can also be automatically visualized, which is why it has this js on like objects notation. Unlike your typical reducer, X State is state first, non events first. It forces you to focus on separating your behavior in terms of finite states, and then specifying the transitions based on events. Now you can do this without X State or any library for that matter, but you would involve things like nested switch statements, and it wouldn't really be easily visualizable. Like I mentioned earlier, a machine can be represented as a reducer. And that's what this machine.transition function is. You provided the current states and the events that just happened and you get the next state.
But what it could also do is provide some event emitter interface in which it contains the state itself and you could send it events and have it manage its own states. And this is really useful in situations where you don't want to have to wire up together where to store the states. And in addition to that, it's also observable. You could use this with RxJS as well, and it doesn't need to be said, but this is completely framework agnostic. You could use it with any framework and it is set up so that it's easily integrated. Whatever the word is. You could use it with any framework, like Reacts, Vue, Angular, Svelte and more.
[09:25] But speaking of React, there's a lot of useful utilities that allow you to more easily use X State machines with React. And one of these is the useMachine hook, and this is just like the useReducer hook.
In fact, if you know useReducer, then you basically already know useMachine. Instead of passing a reducer, you would pass a machine that you created and you get the same two expected values from the twofold, the state, which represents the current state of the machine, and send, which represents a way for you to send events to that running machine.
[10:06] Now, the state also has a few utilities such as matches so that instead of having to figure out what the exact finite state of the machine is, you could just pass in the expected states or part of the state, like first, second review in this wizard form example. And it provides a nice clean way to show different parts of the component. And of course you could also send events, whether it's just the events string or an entire event object. It's really useful and really handy, just use it like you would useReducer.
Recent and upcoming features of X State
[10:46] But I do want to talk about some recent and upcoming features to X State, and X Sate React that I am particularly excited about.
The first one is useInterpret. useInterpret, the goal of this is to interpret a machine, create a service, and it just returns that service. And then that service is a single object, which is a reference to the interpreted version of that machine, which never changes. This makes it really useful in Context. If you were to create Context with React, now you could pass that service into that Context provider and use it wherever you want, such as a wizard. Now, the great thing about this is that since the service doesn't change, it's not going to cause a lot of re-renders. In fact, it only updates once, which is when the service is created. You're guaranteed to never have any extraneous renders. And the way that you would use this is a combination of two hooks useService and useContext.
[11:59] You grab the service from the Context, which is that ServiceContext, and then using the useService hook, you could use it just like a machine. Instead of passing a machine in, you pass the service in and you get the two same expected values from the twofold, the current state and the way to send to that service. And then you could use it as normal. And this gives you the ability to have both local and global states and even semi-local state where you need some states shared between a bunch of components, but not with every component.
And however, even this, sometimes even though it does prevent massive re-rendering due to everything changes, it could also lead to too many renders too, if one of your components doesn't actually care about a certain part of the state. And that's why there's a new hook called useSelector.
[13:03] useSelector takes that service or any actor really, and we'll talk about that in a minute, and you could pass in a selector, which takes in that admitted state's value and it returns only the states that you care about. In this case, we only care about the data for the form, so we call useSelector with service and the selector, and we get the data in return. Now, you could still call service.send to send events to the service, nothing stopping you from doing that. The purpose of useSelector is to limit the number of re-renders doing a shallow comparison on this selector.
And also X State is smart enough that you actually don't need to directly compare or shallow compare objects every single time, because X State knows that when you make a transition, if there's no assign actions happening, then it knows for a fact that that state isn't going to change. It could be a lot smarter about that and improve your application performance a lot. Definitely give the useSelector hook a try.
New features in X State
[14:23] Now let's talk about a bunch of new features in X State itself. And the goal of these features is largely to make it easier to specify and type context, events and in the future, a lot more such as actions, guards, et cetera. One of these utilities is the createModel function.
And createModel just provides a nice way of containing your Context. At first it might seem super fluid, but we'll see some advantages to this in a minute. You pass in your initial Context to createModel, And now you have a userModel. This model represents the extended states or the Context of your machine. The machine defines the finite states, the model can help you define extended states, also known as Context. And one of the benefits of using this model is that there are some utility functions such as assign. Instead of pulling assign from X State as a separate action, now you could just call in this case, userModel.assign, and everything is type safe. And it makes it a lot easier to assign values that way.
[15:43] But, like I mentioned, one of the biggest benefits is using Typescript and making it a lot easier to type. If you specify Context and events into this model, now this assign model will actually be strongly typed and you could even restrict the type of event that this assign can take. And this is inferred properly in the machine, right here, you pass in typeof userModel over here, and it will correctly infer the Context and the events too. And this will correctly warn if it's used in a place where the update name event didn't happen, so you could easily restrict it there.
That might be a little bit for both, which is why one of the more recent changes are these events, creator functions, and they allow you to specify the events type and a neat little factory function for providing the events payload. Now notice that we don't have any geneneric types here, and that's because it's inferred from the Context and it's inferred from these events over here. And this is actually really nice because now instead of providing raw events, you could use these as events factories, and have updateName and updateAge for instance, and this is userModel.events, updateName. I probably should have fixed that, but you get the idea.
What's coming up in the next version of X State
[17:20] Now let's talk about what's coming up in the next version of X State. One of the big changes I'm excited about, which is also part of the SCXML spec already, is partial wild cards.
And this allows you to specify transition for any group of events that have the same prefix. Now, prefixes are delimited a dot. In this case, this transition right here will match mouse. It will match mouse.click. It will match things like mouse.move.out. It won't match events like mouse move if it's one word, it doesn't work that way. It's prefix, according to the SCXML spec, but this just makes it a lot easier to group related transitions together or related events really, into a single transition.
[18:13] Another change I'm excited about is higher level guards. Before, you had to specify guards like if you had some condition, if you wanted that condition to just be the inverse where it's false instead of true, you had to specify a new condition. And now with higher level guards, you could just use these guard creators such as, and, or, or not, and compose them together in many different ways.
There's two benefits to this. First of all, I know what you're thinking, why not just write this in code with if statements and operators like that, and because X State provides you the ability to serialize guards, it works automatically with those serialized guards within the machine, so that you don't have to reference the guard directly. The guard can and should be in implementation detail, and X State allows you to leave it just like that. Also, this allows you to fully visualize these guards in a future version of the visualizer, which will actually show it basically as a flow chart or a decision tree. And that's a really nice benefit of having it specified this way.
[19:40] There's a lot of new changes coming to X State version five. And I'll briefly describe them here. First of all, everything is an actor. Before X State was special casing things like callbacks, observables, promises, other services, and just converting them internally to actors so that you could spawn them, invoke them, et cetera. But now X State version five greatly simplifies this and says, instead of special casing those things, we're taking the stance that if it is an actor or an actor like object, then we could interact with it directly. Anything that has a send and a subscribe method, it's just going to work with it. And it makes it also really easy for you to create your own actors for your own special use cases.
Another change coming is In-order assign(). Previously in version four, in hindsight, this was a bit of a bug, but assign calls are no longer immediately prioritized. Now you can define actions where in a sign call might come later. And so the actions will be reflected properly so that instead of calling assign first, assign is called in order instead. And this is part of the SCXML spec. And it's also technically a breaking change, which is why this is going into the next version of X State five.
[21:15] We've heard a lot that Typescripts might be a little bit painful in X State. And that's because X State is really trying to push the limits of what Typescripts can do. Version five is going to provide a lot stronger and easier typing and better type inference as well. In addition, X State version five is being built from the ground up to be modular and as a result, smaller. You could pull in only the things you need from X State. The internal algorithms have been completely overhauled. And because of the modularity, you could really, really make this as small as you want it to be.
And there's many more improvements coming to X State version five. I encourage you to check out the branch and check out the discussions on GitHub, if you're interested in what's coming next. And the time that this conference talk is aired, hopefully there will be an X State alpha that you could play with.
[22:18] Now, X State has a lot of these benefits of having a visual formalism. First of all, obviously being visualization, but also being able to automatically generate tests, automatically take in analytics and be able to capture all of the transitions and things that can happen and store that somewhere, so that you could do analysis on that later. And also auto generation of things such as testing and type generation and documentation generation, and a lot more than that.
And this is why X State doesn't fall into the normal category of state management tools. It's more state orchestration and it does it in a declarative way to enable all of these things. And that is why I say that we have a visual future for X State because it's a lot more than just being another state management library. One of the recent examples of this is X State catalog, this is actually a really, really great project Matt Pocock, and it allows you just to have this catalog of machines and be able to interact with it directly and visualize it at the same time. And this is the X State inspector, which I highly recommend you checkout too, because it brings new capabilities to visualizing your state and going both ways as well. You can interact with the inspector and have the state change and the other way around. It's like Redux dev tools, but a lot more powerful.
[24:02] When talking about just this visual aspect of code, the fact of the matter is that today a lot of the code we have is very much stuck in code land, where we have code only. And in my opinion, this is hard to work with. We have code that we have to either talk to the developer or read the comments or something, and just try to figure out what this code is supposed to represent, whether it's features or just how the app is supposed to work. And one thing that we could do is have code and diagrams together. But the problem with this is that these diagrams are typically not in sync with the code. And to solve that, we have solutions like code to diagram, where the diagram is generated from the code. But the problem is that it's very much a one way street, whether you do code to diagram or diagram to code. And diagram to code's a little bit worse because it makes it so that you can't really touch the code, but all of these are a step in the right direction in syncing diagrams and codes together.
But the future and the visual future that I see is diagrams and code working together, so that in the future you'll be able to either edit the code and generate diagrams or edit the diagrams and generate code and be able to edit that code as well. We see this in so many different areas in tech, and this is something that I definitely want to bring to the web development world, which is why I'm starting a set of tools called Stately. And this set of tools are going to be really useful for visually creating, inspecting, testing, analyzing, simulating these state machines and state charts for your app. And in the future, won't be restricted to just X States, but will allow you to encompass a lot more logic regardless of what language you write in and be able to share them and to collaborate with other developers and other teammates on it. And if you want a preview of what these tools are and what they could do, go over to stately.ai. So with that, thank you, React Summit.
[26:45] Nathaniel Okenwa: Are you using state machines explicitly in your applications? And if we go and look at the answers, we can see that most people said, yes, a good 38% said "Yes, with other or no libraries." 28% said, "No, they're all implicit." 25 said "Not yet," but they want to. And 11% said "Yes, with X State." Is there any surprising results there that you see David?
[27:13] David Khourshid: Hey, yeah. At first, I was surprised that we have "Yes, with other or no libraries," because at least a lot of people are realizing that state machines are important and explicitly modeling your application logic with state machines is also important. I'm glad that developers are understanding that more and more. And I hope that my talk inspired them to maybe use X State to just make that process a lot easier.
[27:40] Nathaniel Okenwa: It's always like that. We see a developer principle or something become more important. And then there becomes a tool that comes along and just makes it really easy to use. Definitely I'm going to be checking that out. We've got a couple of questions and I'm going to go through them. I'm going to go through one from Vasiliy Shelkov, "Is X State a global state management library and alternative to Native React state management, Redux, Recoil?" They like the idea of having a clear view of state transitions and events, but not so much of global state as they rarely find it necessary. That's a bit of a long one.
[28:17] David Khourshid: Yeah. X State is however you want to make it. It could be used in a global fashion, but the typical use case is using it in local component state or wherever you need it. Even bringing that up and especially with the new Hooks, now you could share those services through components as you need it. In that way, it functions the same as React's Native useReducer and also Recoil in its actorModel. I do want to say that X State is more of a state orchestration library rather than a state management library. There's a difference in comparison there.
[28:55] Nathaniel Okenwa: Yeah, that makes sense. That makes sense. We also have another question from Lobbian, "Are the send parameters typed? Does code completion know about what send parameters are available?"
[29:09] David Khourshid: Yes, it does. If you type your events in the machine, then you could do send, start typing and it'll be able to auto complete the events as you type them and even have the correct parameters if you type them correctly. In version five, this typing is going to be a lot stronger too. So, look out for that.
[29:28] Nathaniel Okenwa: Nice, nice. We're looking forward to that already. And Armanos:, "Does it support middlewares?"
[29:35] David Khourshid: It does not. And the reason is because middleware is a different concept. Basically you could think of X State as a lot simpler than something that needs middleware, X State is something where you send events to and you subscribe to state changes. Now, when those state changes happen, you could do whatever you want with them. That's where your middleware would go if you want such as logging. But also X State declaratively handle side effects too, in the form of actions. And that eliminates a wide class of middleware that you might need. So yeah, the whole actorModel, state machine model, it doesn't use middleware because middleware is too abstract... Or not abstract, but ambiguous and vague. And you can't predict what goes on in middleware. And the whole point of state machines is to be able to predict everything.
[30:26] Nathaniel Okenwa: Nice. We also have another question, which is, "Can you chain send commands or is there a callback or promise on the send?"
[30:37] David Khourshid: Send is fire and forget. You send an event and then the machine receives it and does something. It's the same as if you tell someone to do something, you're not going to wait for them to be like, okay, I understand. Or maybe that's a bad analogy, but yeah. Send is fire and forget.
[30:55] Nathaniel Okenwa: Cool. And another question from Eric, says, "How does utile handle data changes comparison or do you need to use normalized data?"
[31:07] David Khourshid: What you do with extra data is, you're basically free to do whatever you want. You put it in Context and then you could change it. X State is smart enough to know when you actually change the Context data though. You don't need to do big diffs or anything. And also because X State has the actorModel and you could spawn or invoke actors, those can be little containers of data. If you only want to subscribe to changes from those, you could do so individually without having to do a deep comparison of the entire object. So, yeah.
[31:43] Nathaniel Okenwa: Nice, nice. Now, we've got a question. I really like this next question. Imagine there's someone out there in the audience who's listened to this talk and thinks X State is amazing and they want to expose it to their team. This question says, "It feels like they are quite a few new concepts and probably a harder sell for a development team. Have you got any tips for what a good way of trying X State and getting the team of developers interested in trying X State would be?"
[32:09] David Khourshid: Sure. There is an article I wrote called You Don't Need a Library for State Machines. And that article describes how to just get started with using switch statements followed objects, and just getting pretty far along the path of using state machines in your app without too much friction. In fact, you could use the same useReducer or whatever you want. And then it shows how effort listed is to migrate to X State when you need to.
[32:38] Nathaniel Okenwa: Cool. We have another question about performance. One of the first ones is comparing it to other libraries. Since the poll asked if users were using other libraries, they're curious to know how X State compares with some of the other state machine libraries, and what really sets it apart.
[32:54] David Khourshid: Good question. I actually haven't done too much research on that. There are other state machine libraries, but yeah, I haven't done any performance research there. Actually it's fast enough though, I haven't heard anything bad about the performance.
[33:09] Nathaniel Okenwa: Oh, that makes sense. And what is the performance impact of using React use state directly versus using X State?
[33:16] David Khourshid: Well, there's always going to be a little bit of overhead, which is why I say X State is most useful for complex logic, because it's definitely going to outshine trying to do it manually.
[33:29] Nathaniel Okenwa: Nice. And do you recommend one or multiple machines per app? This is a question from Radoslav.
[33:35] David Khourshid: It depends. It depends on your app's needs. If your machine gets too big, then you should try to split it into multiple machines and those machines will act independently. You could think of each of those machines as just little entities that communicate with each other, which makes it really easy to organize your logic.
[33:53] Nathaniel Okenwa: Thank you so much. We were able to fit quite a few questions in that short amount of time. We're going to have to move on. I'll see you later, David. Hopefully I get to see you play piano at some point.
David Khourshid: All right. Thank you.