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.
XState: the Visual Future of State Management
AI Generated Video Summary
This Talk introduces the visual future of state management and how XState is bringing it to reality. It explains the role of reducers in containing application logic and how state machines and state charts provide a visual and mathematically rigorous way to represent behaviors. XState is easily integrable with React and other frameworks, allowing for the management of local, global, and semi-local state. The Talk also highlights new features in XState, such as the use of TypeScript for stronger typing and the introduction of the useSelector hook for improved performance. The future vision includes the combination of diagrams and code, with tools like Stately.ai, and the importance of explicit modeling of application logic using state machines.
1. Introduction to Visual Future of State Management
Hey, everyone. My name is David Courchide. I want to talk to you today about the visual future of state management and how XState is going to bring that future vision to reality. State machines and state charts are visually exact diagrams that convey relationships in a visually unambiguous way. They have their own special notations and are mathematically rigorous languages. I recommend reading David Harrell's paper on visual formalisms for more information.
Hey, everyone. My name is David Courchide. I go by David K. Piano online on Twitter, GitHub, whatever. And I want to talk to you today about the visual future of state management and how XState is going to bring that future vision to reality.
So talking about visuals, you probably know what a Venn 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. So, 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 so, with state charts, we have the same type of thing. We have arrows and boxes just describing how states and logic can flow over time. David Harrell, who is the inventor of state charts, calls this a visual formalism. And so, he describes that visual formalisms are diagrammatic and intuitive, yet mathematically rigorous languages. Thus, despite their clear visual appearance, they come complete with a syntax that determines what's allowed and the semantics that determine what the allowed things can be. And so, 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.
2. Coding Logic and the Role of Reducers
The way we typically code application logic doesn't lend itself to a visual formalism. The logic is hard to understand and centralize. Reducers provide a way to contain logic in a centralized location, forcing interaction through events. State machines separate behaviors into finite states, representing current states and responses to events.
So, 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 co-locate 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.
So, while this may be convenient to code, the problem 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. 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 so, that connection logic resides in the head of the developer who added the logic, which isn't really useful, and so you end up with things such as ad hoc logic as well, you know, 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, you know, itself being ad hoc logic, just living somewhere else. So you're still not centralizing everything, which, you know, definitely becomes a bit of a problem.
And so enter the reducer, popularized by Flux and state management libraries like Redux, reducers provide a way to contain this logic in a centralized convenient location. So one hugely important beneficial constraint of reducers is that it forces you to interact with the logic by sending events or actions, as they're called in React and Redux. By the way, the naming of actions was sort of a mistake, at least in my opinion. So we're going to be using the term events in this presentation. But here is 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. So 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.
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. And this becomes a lot more difficult. It mixes those switch statements and those if statements. And so you have to piece together the logic by 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.
So 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 can respond to events. So it might respond to an event by performing an action or by changing its behavior or anything like that. And that's represented by these transition arrows that go from state to state. Or an event might not be handled, in which case the default behavior is to ignore that event.
3. State Machines, State Charts, and Xstate
State machines in reducers require defensive code, but state machines prevent impossible states and transitions. State charts extend state machines by introducing hierarchy, grouping related states and transitions. Xstate was created to represent state machines and charts in a clean and visualizable way, with a state-first approach. It uses a JSON-like object notation.
In reducers, this often requires a lot of defensive code, but with state machines, it's built right into the mathematical model. And more practically, this kind of 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 know, you can't have any implicit transitions.
State charts take this idea of a visual formalism one step further by introducing hierarchy. Although state machines provide a way to cleanly organize 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 HIGRAPH as David Harrel 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 machines, 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.
So that's a state chart. And so this is why I created Xstate a few years ago. I wanted developers to be able to represent state machines and state charts in a way that can be coded cleanly, and can also be automatically visualized, which is why it has this JSON-like object notation. So unlike your typical reducer, Xstate is state-first, not events-first. It forces you to focus on separating your behavior in terms of finite states and then specifying the transitions based on events. You can do this without Xstate or any library, for that matter, but it would involve things like nested switch statements, and it wouldn't be easily visualizable.
4. Using X State with Reducers and React
A machine can be represented as a reducer, providing an events emitter interface to manage its own state. It is framework agnostic and easily integrable with React and other frameworks. The use machine hook is similar to the use reducer hook, allowing you to pass a machine and get the current state and send events. The state also provides utilities like matches for showing different parts of the component. Recent and upcoming features include use interpret, which creates a service that can be used in context without causing unnecessary rerenders. By combining use service and use context hooks, you can have local, global, and semi-local state. However, be cautious of too many renders if a component doesn't care about a certain part of the state.
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 state and the events that just happened, and you get the next state. What it could also do is provide a sort of events emitter interface in which it contains the state itself and you could send it events and have it manage its own state. This is really useful in situations where you don't want to have to wire up together where to store the state. In addition to that, it's also observable. You could use this with RXJS as well.
It doesn't need to be said, but this is completely framework agnostic. You could use it with any framework. It is set up so that it's easily integrable, whatever the word is. You could use any framework like React, Vue, Angular, Svelte, and more. But speaking of React, there's a lot of useful utilities that allow you to more easily use X state machines with React. So one of these is the use machine hook, and this is just like the use reducer hook. In fact, if you know use reducer, then you basically already know use machine. So instead of passing a reducer, you would pass a machine that you created, and you get the same two expected values from the tuple, 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. 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, like, 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 event string or an entire event object, so it's really useful and really handy, you know, just use it like you would use reducer.
But I do want to talk about some recent and upcoming features to X state and X state React I'm particularly excited about. The first one is use interpret. So use interpret, the goal of this is to interpret a machine, create a service, and it just returns that service. And that service is a single object, which is a reference to, you know, the interpreted version of that machine, which never changes. And so this makes it really useful in context. So 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 rerenders. In fact, it only updates once which is when the service is created. So you're guaranteed to never have any extraneous renders. And so the way that you would use this is by a combination of two hooks, use service and use context. So you grab the service from the context, which is that service context, and then using the use service 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 tuple, the current state, and the way to send to that service. And then you could use it as normal. And so this gives you the ability to have both local and global state, and even semi-local state where you need some state shared between a bunch of components, but not with every component. And however, even this, sometimes, even though it does prevent like massive re-rendering due to everything changes, it can also lead to too many renders if one of your components doesn't actually care about a certain part of the state.
5. Introducing the use Selector Hook
There's a new hook called use selector that allows you to retrieve only the states you care about. It limits rerenders by doing a shallow comparison and improves application performance. Give the use selector hook a try.
And so that's why there's a new hook called use selector. Use selector 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 emitted states value and it returns only the states that you care about. So in this case, we only care about the data for the form. So we call use selector 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 use selector is to limit the number of rerenders by doing a shallow comparison on this selector. And also, xdata 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 action happening, then it knows for a fact that state isn't going to change. So it could be a lot smarter about that. And improve your application performance a lot. So definitely give the use selector hook a try.
6. New Features in X State
Now let's talk about the new features in X state, such as the creates model function for containing context, the utility functions like assign, and the use of TypeScript for stronger typing. The transition type allows for grouping events with the same prefix, making it easier to define related transitions. Higher level guards provide more flexibility by using guard creators like and, or, and not. These changes make specifying and typing context, events, actions, guards, and more easier in X state.
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, etc.
One of these utilities is the creates model function. Create model just provides a nice way of containing your context. So at first it might seem superfluous but we'll see some advantages to this in a minute. You pass in your initial context to create model and now you have a user model. This model represents the extended state or the context of your machine. So the machine defines the finite states, the model can help you define extended states. Also known as context.
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 can just call in this case user model.assign and everything is type safe, and it makes it a lot easier to assign values that way. But like I mentioned, one of the biggest benefits is using TypeScript and making it a lot easier to type. So, 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 type allows you to specify transition for any group of events that have the same prefix.
Now, prefixes are delimited by a dot. So, 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 by prefix according to the SEX and mouse spec. But this just makes it a lot easier to group related transitions together. Or related events, really. Into a single transition. 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.
7. Benefits and New Features of X State
XA provides the ability to serialize guards and visualize them in a future version of the visualizer. XState version 5 introduces changes such as treating everything as an actor, in-order assign, stronger and easier typing in TypeScript, and modularity. X state has a visual future, enabling automatic test generation, analytics, and more. X state catalog and X state inspector are recent examples of the power of visualization.
Why not just write this in code with, you know, if statements and operators like that. And because we're, XA provides you the ability to serialize guards, it works automatically with those serialized guards within the machine so you don't have to reference the guard directly. The guard can and should be in implementation detail and XA 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 flowchart or a decision tree. And so that's a really nice benefit of having it specified this way.
So there's a lot of new changes coming to XState version 5 and I'll briefly describe them here. First of all, everything is an actor. Before XState 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, etc. But now, XState version 5 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 can interact with it directly. So that's 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. So previously in version four, in hindsight, this was a bit of a bug, but assign calls are no longer immediately prioritized, so now you can define actions where an assign 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 SCX ML spec, and it's also technically a breaking change, which is why this is going into the next version of X state five. We've heard a lot that TypeScript 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. So 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. So 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 by the time that this talk is aired, hopefully there will be an X state alpha that you could play with.
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 can 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 so 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 so that's 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, which this is actually a really, really great project, um, and, uh, by Matt Pocock. And, uh, it allows you to, um, just to have these 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, um, I highly recommend you checkouts too, because it brings new capabilities to visualizing your state, uh, and going both ways as well.
8. The Visual Aspect of Code
Today, a lot of the code we have is very much stuck in code land where we have code only. 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.
So you can interact with the inspector and have the state change and the other way around. So it's, um, it's like Redux dev tools, but a lot more powerful. Um, so when talking about, you know, just this visual aspect of code, the fact that now 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 this is hard to work with, right? 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, um, you know, just how the app is supposed to work.
9. The Visual Future of Code and Diagrams
The future I see is diagrams and code working together. I'm starting a set of tools called Stately for visually creating, inspecting, testing, analyzing, simulating state machines and state charts. Check out stately.ai for a preview of these tools. In the Q&A session, developers expressed their understanding of the importance of state machines and explicit modeling of application logic. Xtate can be used globally or in local component state, functioning similarly to React's use reducer and recoil. Xtate is more of a state orchestration library than a state management library.
And so, um, 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 so, to solve that, we have solutions like Code2Diagram 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 is 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, in the visual future that I see is diagrams and code working together so that, you know, 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. And so 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 these 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, to share them and to collaborate with other developers and other teammates on it. And so if you want a preview of what these tools are and what they can do, go over to stately.ai.
So with that, thank you, React Summit. How 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 sort of results there that you see, David? Hey, yeah. At first, I was surprised that we had yes with other or no libraries, because at least a lot of people are realizing that, you know, state machines are important and explicitly modeling your application logic with state machines is also important. So I'm glad that developers are understanding that more and more and I hope that my talk inspired them to maybe use x states to just make that process a lot easier. It's always like that, right? We see sort of a developer principle or something become more important and then it becomes a tool that comes along and just makes it really easy for you. So definitely I'm going to be checking that out. So we've got a couple of questions and I'm going to go through them. So I'm going to go through one from Vasily Shchelkov. Is xtate a global state management library, an 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. Split from one. Yeah, so xtate 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. So even bringing that up, and especially with the new hooks now you could share those services through components as you need it. So in that way it functions the same as React's native use reducer and also recoil in its actor model. I do want to say that xtate is more of a state orchestration library rather than a state management library. So there is a difference in comparison there.
Send Parameters, Middlewares, and Data Changes
Yeah, the send parameters are typed, and CodeCompletion knows about them. In version 5, the typing will be stronger. Xdate does not support middlewares, as it aims to be simpler and handles side effects declaratively. Send commands are fire and forget. Xstate is smart enough to handle data changes without the need for big diffs or normalized data.
Yeah, that makes sense, that makes sense. We also have another question from Lulbian. Are the send parameters typed? Does CodeCompletion know about what send parameters are available? Yes, it does. So 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 5, this typing is going to be a lot stronger too. So look out for that.
Nice, we're looking forward to that already. And Arman asks, does it support middlewares? So it does not, and the reason is because middleware is sort of a different concept. Basically you could think of Xdate as a lot simpler than something that needs middleware. Xdate is something where you send events to, and you subscribe to state changes. Now when those state changes happen, you can do whatever you want with them. So that's where your middleware would go if you want, such as logging, but also Xdate declaratively handles side effects too in the form of actions. And so that eliminates like a wide class of middleware that you might need. So yeah, the whole actor model, state machine model, it doesn't use middleware because middleware is too, you know, 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.
So we also have another question, which is, can you chain send commands or is there a callback or promise on the send? Send is fire and forget. So 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, like, you know, you're not going to wait for them to be like, okay, I understand. Maybe that's a bad analogy. But yeah, send is fire and forget. Cool. Another question from Eric says, how does this util handle data changes comparison or do you need to use normalized data? The way, like what you do with extra data is you're basically free to do whatever you want. You put it in context and then you can change it. Xstate is smart enough to know when you actually change the context data though. So you don't need to do big diffs or anything. And also because xstate has the actor model and you could spawn or invoke actors, those can be little containers of data. So if you only want to subscribe to changes from those, you could do so individually without having to do a deep comparison of like the entire object. So yeah. Nice.
Introducing xstate to the Development Team
To introduce xstate to your team, start by using switch statements and objects as a way to get started with state machines. There is an article called 'You Don't Need a Library for State Machines' that explains this approach. It demonstrates how to gradually transition to xstate when the need arises.
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 xstate is amazing. And they want to kind of expose it to their team. So this question says, it feels like they are quite a few new concepts and probably a harder sell for development team. Have you got any tips for what a good way of trying xstate and getting the team of developers interested in trying xstate would be? Sure. So 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 by 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 use reducer or whatever you want, and then it shows how effortless it is to migrate to xstate when you need to.