David Khourshid

XState: the Visual Future of State Management

Conference talk at React Summit Remote Edition 2021. 27:00 long, 2.1K views on YouTube. Topics: visual modeling, xstate, state management, react, event-driven architecture, statecharts.

Watch on YouTube (React Conferences by GitNation)

What is this talk about?

“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.”

“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.”

From the opening of the talk.

Key takeaways

  1. 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. 0:00
  2. 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. 4:00
  3. The goal of these features is largely to make it easier to specify and type context events. 14:00
  4. XState version 5 greatly simplifies this and says, instead of special casing those things. 20:00

Quoted from the transcript.

Full transcript

From YouTube auto-captions. Punctuation and capitals were restored automatically. Technical terms were normalized; some errors remain. 3,856 words.

0:00 Introduction

Hmm. [Music] Hey everyone, my name is David Khourshid. 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 statecharts, of course, which I've been talking about for a while, and state machines and statecharts fall under this. 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 statecharts, we have the same type of thing, we have arrows and boxes just describing how states and logic can flow over time. David Harel, who is the inventor of statecharts, 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.

Play from 0:00

2:00 Although state machines provide a way to cleanly organize logic…

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. 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 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 convenience 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.

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 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. Uh. 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 to forces you to interact with the logic by sending events or actions as they're called in React and Redux lines. 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'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, 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 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.

To, 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, you know, 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 could respond to events. So it might respond to an event by performing an action, or by changing its behavior. Or, or anything like that. And that's represented by these transition arrows that go from state to states. Or an event might not be handled in which the cases that, 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 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. Statecharts 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 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 machines, statecharts are also mathematically rigorous, but they can express a lot more complex logic than a state machine can.

And it solves the problem of a combinatorial explosion within state machines. Because it allows you to group related behaviors together, so that's a statechart, and so this is why I created X data a few years ago. I wanted developers to be able to represent state machines and statecharts in a way that can be coded cleanly and can also be automatically visualized. Which is why it has this JSON-like object notation.

Play from 2:00

8:00 You grab the service from the context…

So, unlike your typical reducer, X8 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. Now you can do this without XData or any library for that matter, but it would involve things like. Nested switch statements. And it wouldn't really be easily visualizable. And so, 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, you get the next state, um. But what it could also do is provide some, like a sort of event emitter interface in which it contains the state itself. And you could send it events. And have haven't managed its own state. And this is really useful in situations where you don't want to have to wire up together where to store the state.

And in addition to that, it's also observable, so 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 integratable, integrable. Whatever the word is, you could use it with any framework like Reacts, View, Angular, Spelt, and more. But speaking of React, there's a lot of useful utilities that allow you to more easily use XD machines with React. And so one of these is the useMachine hook, and this is just like the used reducer hook. In fact, if you no useReducer, then you basically already know useMachine. 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 it's really useful and really handy. You know, just use it like you would useReducer. But I do want to talk about some, recent and upcoming. Features to X8 and X8 React that 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 then 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 a, 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 re-renders. 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 states, and even semi-local state where you need, 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 like massive re-rendering due to everything changes. Uh. 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 so that's why there's a new hook called useSelector. You selector takes that service. Or any actor, really, and we'll talk about that in a minute. And you could pass in the 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 you 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 useSelector is to limit the number of re-renders by doing a shallow comparison, on this selector. And also, XD is smart enough that you actually don't need to directly compare or shallow compare objects every single time. Because X8 knows that when you make a transition, it, you know, if there's no assign actions 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 you selector hook a try. Now, let's talk about a bunch of new features in XState 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, cards, etc. One of these utilities is the create model function, and so create model just provides a nice way of containing your, 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 states or the context of your machine, so the machine defines the finite states. The model can help you define extended states again, also known as context. And so, one of the benefits of using this model is that there are some utility functions, such as assign. So, instead of pulling a sign from XState as a separate action, now you could just call, in this case, user model dot assign. And everything is type safe. And you know, 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, um.

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 of user model over here and it will. Correctly infer the context and the events, too. And so this will correctly warn if it's used in the place where the update name events didn't happen, so you could easily restrict it there. So, you know, that might be a little bit verbose. Which is why one of the more recent changes are these event 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 generic types here, and that's because it's inferred from the context and it's inferred from these events over here. And so this is actually really nice.

Because now, instead of providing raw events, you could use these as events factories and have update name, and update age, for instance. And this is user model..events.updatename. I probably should fix that, but you get the idea. Now, let's talk about what's coming up in the next version of XState. One of the big changes I'm excited about, which is also part of the SCXML spec already, is partial wildcards. And so this 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 dot click. It will match things like Mouse.move.outs, it won't match events like Mousemove.

Play from 8:00

18:00 XState version 5 greatly simplifies this and says…

If it's one word, it doesn't work that way, it's by 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. 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, you know, 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, you know, use these guard creators such as and 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 you know if statements and operators like that? And because we're, you know, X8 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.

You know, the guard can and should be in implementation detail. And next, it 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, a lot of new changes coming to X8 version five. And I'll briefly describe them here. First of all, everything is an actor. Before X8 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 could 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 4. In hindsight, this was a bit of a bug. But assigned calls are no longer immediately prioritized, so now you could define actions where an assigned call might come later. And so the actions will be reflected properly. So that instead of calling a sign first, a sign is called in order instead, and this is part of the SCXML spec. And it's also technically a breaking change, which is, you know why this is going into the next version of Backstage five. We've heard a lot that typescripts might be a little bit painful in next day, and that's because XC is really trying to push the limits of what typescripts can do.

So, version 5 is going to provide a lot stronger and easier typing and better type inference as well. In addition, X8 version 5 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 XState? 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 X8 version 5.. 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 conference talk is aired, hopefully there will be an XState Alpha that you could play with now. X8 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.

In, take in analytics, and be able to capture, all of the transitions. And you know things that can happen and store that somewhere, so that you could do analysis on that later. And also auto generation of, you know, things such as testing and type generation and documentation generation, and a lot more than that. And so this is why XState doesn't fall into the normal category of state management tools.

Play from 18:00

23:00 The future and the visual future that I see is diagrams…

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, you know, we have a visual future for XState because it's a lot more than just being another state management library. One of the recent examples of this is X8 Catalog, which this is actually a really, really great project. And by Matt Pocock. And, it allows you to, 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 XD inspector, which, I highly recommend to you checkouts too, because it brings new capabilities to visualizing your state. And going both ways as well, so you can interact with the inspector and have the state change and the other way around. So it's, it's like Redux Dev tools, but a lot more powerful. So, when talking about, you know, just this visual aspect of code, the. 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 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, you know, just how the app is supposed to work. 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 code 2 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 cote 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, 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. 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 statecharts for your app. And in the future, won't be restricted to just XState, 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, um. 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 [Music].

Play from 23:00

Related talks