# The two ways to manage state

Canonical: https://davidk.dev/talks/the-two-ways-to-manage-state/
Video: https://www.youtube.com/watch?v=yVhmYiRNPvw
Speaker: David Khourshid (@DavidKPiano), creator of XState
Type: Conference talk · Event: JSHeroes 2023 · Year: 2023 · Duration: 27:53 · Views: 632 (as of 2026-10-19)
Topics: State management, Actor model, Event-driven architecture

## Summary
There are two kinds of state management libraries: easy and simple. Direct and indirect. Quick and maintainable. When you’re quickly prototyping apps or writing robust app logic, which do you choose? Is there a right answer? In this talk, we’ll traverse the evolution of state management, from mutation to statecharts and signals and take a walk through the state-of-the-art state libraries.

## Key takeaways (quoted from the transcript)
- If you don't think that managing state is difficult, then you know, you're probably either creating simple applications, or you're sort of just slowly reinventing other ways of managing state. (1:00)
- The next realization is that effects management is actually state management, so we might think, okay, when you're given a state in an event, you get a next state. (8:00)
- We could actually model the behavior of an actor, both how the state updates and how its behavior changes. (14:00)
- Otherwise, switch statements are a great way to model simple state machines. (18:00)

## Chapters
- 0:00 Introduction
- 2:00 Let's say that you have two different things wanting to update…
- 6:00 Updates in terms of the next state in effects is a…
- 11:00 The actor model is actually pretty simple to think about
- 21:00 Cappuccino does not exist in this code base…
- 24:00 We talked about a lot of things mutable versus immutable…

## Transcript
[0:00] Hello everyone, my name is David Khourshid. I am at David KPN and pretty much everywhere online, and I work at Staley..ai. And that's pretty much all you need to know about me. I am super excited to be in Romania and to be at JS Heroes. It's an amazing conference. You have ice cream, you have amazing food, amazing people, you have coffee, you have like two or three espresso machines, which? That's my love language.

[0:00] It took me 24 hours to travel here, I'm from Florida, so, it was a hell of a flight. And honestly, managing conferences is not easy, so please let's give it up for the organizers just for this amazing conference. But let me tell you what else isn't easy, and that's managing state. It's super difficult. If you don't think that managing state is difficult, then you know, you're probably either creating simple applications, or you're sort of just slowly reinventing other ways of managing state.

[1:00] And you might think, Okay, this is my state management, this is all I need. By the way, if you program in framework, other than React, this is React use dates. You know, I'm sure that there's similar ways of doing this in other Frameworks, but we know that as we develop our applications, it's never this easy. Usually, it becomes this right. And we have a million new states. And don't even get me started on useEffects, right?

[1:00] But you know what, let's actually not talk about, React. I'm going to give you a simple example of making a counter, the simplest kind of state management example, right? So we're not even we're actually. This is using a really cool language called JavaScript. I don't know if any of you heard of it.

[2:00] Trust me, if you're using TypeScript, reacts Felts. Whatever, you're not really using JavaScript. More on that later. Joe is going to talk about that, but yeah. So we have accounts and we have two buttons over here, and each of those buttons is going to increments and decrement the counts. This is as simple as you could get, and even though we're not using TypeScript, I know that this code correct is correct. Because, chat, GPT gave it to me.

[2:00] So we have two things going on again, as simple as you could. Get, we're either incrementing the count or we're decrementing the counts. And then we're rendering, the counter, which you could just imagine as an HTML element or a div that's just showing the counts. But let's make things just a little bit more complicated, let's add Min and Max values. So all of a sudden, now we have logic in each of these components, so we might want the counts to be no less than zero and no greater than 10.

[2:00] So we could just add in this statement, there no big deal right now. Let's say another requirement comes in where we don't want to just press buttons to increment and decrement, but we also want to do it on key presses. So when you press arrow up, arrow down, then you know, we should also increments and decrement the count. But you see, there's already a problem here, and then that's that we're duplicating logic, so we have to put the count less than 10 or count greater than zero inside of that.

[3:00] But as developers, we're smart and we think, you know what, we could just put that in the function, right? So now we have separate functions for incrementing and decrementing the counts. And we've abstracted this properly so that we don't have to duplicate that logic. This is just good practices, you know, don't repeat yourself code. But now let's do something different. Let's say that we want the counts to show up in two different places.

[3:00] And so as developers do, we think, okay? We're not just going to duplicate it for each place? We want to create some way of sharing the states between these components. So I made a very simple observable over here, which, by the way, if you want to make your own homies observable, this is a really easy way to do it. You just have a set of listeners and whenever you subscribe, you add that listener to that set, and, of course, you provide a way to unsubscribe that listener.

[4:00] And so that way. Whenever you set the new value, each of those listeners are going to be notified. And so the results of this is, we could just set the count in each of these handlers, and so it becomes pretty easy. But there's a problem with this, and that's called a race condition, so. The race condition is that. Let's say that you have two different things wanting to update the counts and or increments or decrement accounts.

[4:00] So if one thing says, you know, the COunt's at one and I want to increment it to two, and another thing C, is the counts as one. But it also wants to increment it. But maybe there's a set timeout and it wants to do it at a later time. Then both are going to set it to two, even though you're really wanting to increment twice and go from two to three. So how do we fix this?

[5:00] Instead, we use events, and so with events, we could just say I want to increment or I want to decrement. And then we have this transition function that lets us handle each of those events so that we could avoid those race conditions. And so now we're only sending those events. So in each of the event handlers, we might be sending Inc. Or we might be sending Deck for increments and decrements, and so the count is going to, you know, increment properly.

[5:00] And so at this point, you just invented Redux or reinvented rather, all right, so a lot of you might be thinking, you know, I don't need the state management library again. These are the people who think, you know, state management is simple. But eventually we get to the point where it's like, you know what? I really should have used a state management library.

[6:00] It always gets to that point and honestly, there is a ton to choose from. I maintain the one up on the top over here, called XState, and this library is almost eight years old. So I have a lot of opinions and things that I've learned from managing all kinds of state, and so I sort of want to share these realizations with you. The first one is that mutable state is best in. immutable state is maintainable, whereas it's easy to just increment accounts or directly manipulate a value.

[6:00] Sometimes, using immutable states, or actually most of the time, is actually more maintainable. And that's because when you mutate state, it becomes difficult to track where the state is actually changing. So you don't have a way of, you know, you don't have a way of knowing what actually. Causes dates to change, it's completely invisible, and so with immutable state, you could actually have a reference to the previous dates. And so that's why a lot of Frameworks trended towards using immutable states, but that's not without its difficulties.

[7:00] With immutable state, you have more complexity, more memory usage, and with meaningful state, there's still a lot of great use cases for it. Because, again, it's fast, you could directly manipulate values, whereas with immutable state, you have to actually do either. Shared structure mutation or not shared structure mutation, but shared structure changes. In order to, you know, get similar performance benefits, but it's never going to be as fast as mutating state, uh.

[7:00] But we've seen recently that there's actually more of a trend towards proxies. And so, there was a talk yesterday, the first talk, which was about this as well. With Angular using signals, and so, signals really fit into this. And so we're actually trending backwards, where developers are now just pretending to mutate things or just doing things more directly. And because we are using a proxy, we're actually able to efficiently update, either by transparently doing immutable state updates on the side, or doing safe mutation.

[8:00] Where everything involved actually knows that. The value has changed. So, with the Getter, whenever we read in a components framework, we're creating a subscription, and so when we, when we actually write to the value. Because we're using a proxy again, we could update the observers and everything makes sense. And there's also libraries like M, which allow you to pretend to do mutation. And then it's actually immutably updating the state, so it's just useful to know that there's these different techniques for updating data.

[8:00] The next realization is that effects management is actually state management, so we might think, okay, when you're given a state in an event, you get a next state. But one of the biggest realizations that I made in maintaining XState was that effects are also definitely part of state management. Even though we might not think so, and we might think, okay, side effects don't belong in these update functions. Well, actually. It's very useful for them to be modeled that way, even if they're not executed directly inside.

[9:00] Thinking about state. Updates in terms of the next state in effects is a really useful mental model. And so in this example, let's say that we're toggling a video where you see a thumbnail and then you see the bigger video. It's actually really useful. And this is sort of pseudocode, to just pretend that play video and pause video are directly inside of that update function. Because, think of the alternative if you don't do this, then you have to do it in a useEffect, or you have to do it in middleware.

[9:00] And all of a sudden, those effects are separated from the thing that actually causes those effects. So the, you know, it's my much more of a mental leap to try to understand when do these effects actually run? And so that's why this is a state machine, and I'll talk about that a little bit later. But it's why it's useful to have just some sort of declarative representation of effects when state transitions happen.

[10:00] So over here, we could see that when I enter the full states. We're playing the video, when I exit, we're pausing the video. And when the video ends. After two seconds, then, you know, the screen is going to close and go to Mini. So that's why, as a useful meant to model, just consider state and effects are actually together, all right. So the next realization has to do with multi-store versus single store.

[10:00] So a lot of libraries, such as Redux sustains few X Pena. They actually operate on the single store principle, where they have stayed at the global level. And then there's a lot of other libraries which operate on a multi-store level, where you could create many instances of these state stores. And they live in many places.

[11:00] So the thing I realize is that while single store is really convenient, multi-store is realistic, and that's because a single Global store is actually a lie. You could have states that actually comes from many different sources, including your database, in API and even within the browser. You know, you could consider your mouse a database, like the way your mouse moves, the pointer X and pointer Y. You know. And other things too, like web sockets.

[11:00] Web workers like state could live in many, many different parts of your app. And. You could even logically separate the parts of your app where state lives and where state is updated. And so that's why one of my favorite ways to think about this is called the actor model. And so just to demonstrate in showing you how much I love coffee. Let's say that you want a coffee, so you ask the barista, and may I have a coffee?

[11:00] The barista tells, or actually just this is the cashier, I don't know. It's a guy with a beard. And, they tell the barista, Okay, this person wants coffee, and so the cashier says, all right, it costs this much. I don't know what the currency looks like here, so I just use euro. I'm sorry if that offends anyone, and the person pays with cash. I should have used a credit card that's Universal, but anyway.

[12:00] So then the cashier gives a receipt, says, Okay, here you go. And eventually the barista prepares the coffee, gives it to the cashier, and then the cashier gives it to, you know, to the person working on their computer. And so this is a way that we can model real life interactions, but also this actor model is just really useful for modeling applications, where we have just a system of, transferring messages between different actors.

[12:00] And the actor model is actually pretty simple to think about. Because there's really two parts. There's sending an event. Did I pronounce that? right? Probably not, but also receiving an events. And I actually don't know how you respond to that, so I just put three dots there. So, if you could tell me afterwards, that would be great, and the active model is actually a really great mental model, like you don't need to have an actor model framework or anything to really understand it.

[13:00] It's just a few simple rules the first one is that all computation is performed within an actor. So that means that when state is updated, it's only updated by the actor itself, it's not updated externally. Like, we can't modify the actor's date, it's privates to the actor, uh. The second one is that actors can communicate only through messages. So just like you and I can communicate, and we can't read each other's minds, we communicate through message.

[13:00] Passing and in response to a message, an actor can change its state or behavior so it could update its data. It could decide to do something else, like if you're asleep and an alarm goes off, now your behavior has changed to waking up. Hopefully, you could also send messages to other actors in response to receiving a message, and you could also spawn new actors. So, yeah, this is the actor model. It's just a way of message passing and a way of separating your state.

[14:00] Into, I guess. You could call them stores, but these stores are alive. They actually send messages, receive messages and do things based on those messages. And so one of my favorite ways to represent actor logic, sorry, is by using a state machine. And so by using a state machine. We could actually model the behavior of an actor, both how the state updates and how its behavior changes. And so these actors, which can spawn each other, this is known as an actor system.

[14:00] And so the reason I'm basically explaining all this, just in a really quick way, is that I believe that the actor model is a great way to model application logic. And so that just means, like I said, separating your states into different entities, different domains, so you could think of domain driven design in this way. And so this helps you organize your state and provide that separation of concerns. Even better when you think of things in terms of sending events, receiving events, and having an actor have its own self-contained behavior.

[15:00] Now, don't go crazy. And, you know, do like the you know, microservice thing, but you know, we, we don't want to model all of our front-end applications as microservices. The next realization is that direct state management is easy, and indirect state management is simple, and what I mean by this is by direct. We're directly updating values, so just like we saw in the first example, we're updating the value of the counts.

[15:00] And we saw what kind of problems that cost and how much duplication of logic we needed. You know, for that with indirect, we're sending events, so these events are handled by some centralized place that says, Okay, I have this event and now I'm going. To decide myself what to do with that event once I receive it based on the current state. And so that way, our logic is simplified to two things.

[16:00] Where either reading states, either through subscriptions or otherwise, and we're updating state indirectly by sending events. And so it doesn't matter how complex your application is, it basically comes down to just these two things, so again, don't model your application like. This is Netflix, it's not a coronavirus, and this is how Netflix does her microservices. But speaking of that, the other realization I made is that everything is a graph, whether it's GraphQL.

[16:00] And this is just modeling relationships between different entities or a flow. And so this is just a simple user flow that determines where a user should go next. This is actually pretty much a state machine. Or, if you're slack, this is how to determine if you should send a notification or not. I didn't realize it was this complicated. I usually mute all notifications, so this is useless. But yeah, there, there you go.

[17:00] And so I realized that, everything is. A graph really applies, even when we're specifying requirements in natural language, so, for example, if a user is logged out, we're representing this as a state in the state machine. When the user logs in with correct credentials, which is represented by that event in transition, then the user should be logged in. And so we have a nice declarative way of representing this as a directed graph.

[17:00] And so this is useful both at the higher level, where we could see, okay, given this, when this, then that, and also at the lower level. Because we could just fill in the implementation details in a way that's useful to developers and so you don't actually need a library for state machines, game developers, developers everywhere have been doing steam machines for the longest time. With things just like switch statements, the difference is that you're basing what you're switching on the states, the finite state, and then the events are not just the events.

[18:00] But otherwise, switch statements are a great way to model simple state machines. Or you could use object lookup instead, and so with object lookup, you're basically making a switch statement in just a more declarative way. And then when you use. Your transition function, you're looking up the states, what the event is, and that will hopefully tell you the next state, otherwise you could default to the previous date. And so I just want to show you a demo of really how far we can take this idea of declarative state management.

[19:00] And you know, just what's really possible with thinking about your same management in this way? We're we're gonna pray to the demo gods and hope that this demo works. But I basically modeled a very simple espresso machine, and so I modeled this using XState, which again library I created. And the way this works is actually, let me just visualize this for you, the way this works is that. We have a bunch of states over here.

[19:00] And so we're going to start in idle. And then, you know, we're going to do things like grind the beans. Not sure if you could see that really well. Yeah, and so after we grind the beans, this isn't actually making an espresso. You could hook it up to, like an IOT device or something that actually makes it. We're heating the water and then we're tamping. And so this machine is consumed by a React app, and this is using the useMachine hook, where I'm passing in the espresso machine.

[20:00] And so the results of that. First of all, one of the cool things is that in this app, these buttons are actually dynamically rendered based on the next events that are possible. And so that's why I really think that, you know, declarative state management and modeling based on events is really, really useful because we could just dynamically render these. Grind some beans, heat the water, Tamp the beans, and then now we have an espresso shot.

[20:00] But I, you know what? Right now, there's a whole craze with AI, large language models and things like that, and so I wanted to take this a bit further. So, because we have this as a directed graph and as an event based thing. I'm going to be using that same espresso machine. Get the shortest paths, and, based on the shortest paths.

[21:00] I provide a prompt to OpenAI that, you know, I say, it's a helpful AI assistant. You have to really pep. Up, OpenAI and say, you're, you're a good, good Bots, good AI agent. And I say, you know how to make coffee, and so I. I also ask if I want to achieve this goal, and then I provide the prompt. Which of the following end states matches that goal best? Only choose one.

[21:00] And so by doing this, we pass in something like I would like a cappuccino, please, or something like that. And it will figure out the path through that exact same state machine for how to make that. This works like 90 percent of the time, so if it gives a funny result, then, you know, don't blame me, that's, that's AI's fault. But let's go ahead and try it. What would I like? So, by the way, I'm gonna search for cappuccino here.

[21:00] Cappuccino does not exist in this code base, so I would like a cappuccino, please. And hopefully the Wi-Fi works, it's going to OpenAI, it's thinking about it, and then we'll see what happens. Is my Wi-Fi on? Should be we, we shall see. Come on, I'll give it five more seconds, five, four, three, two. It's, yeah, all right, all right, you know what a Capu Chino. Now, please, we'll see if it works.

[22:00] Yeah, it's probably just the Wi-Fi. Not being that. Oh, wait, wait, wait, wait, here we go, something's happening. Don't cheer. Yeah, we don't know if we're gonna get our cappuccino yet, all right, so we made the espresso, it added cream and chocolate. Okay, so now it's probably because I said, now, I screamed at it. It got all freaked out and it's like, Okay, I have no idea what you want, so I'm just gonna put ingredients together.

[23:00] But actually, I'm sort of glad it failed because it proved that it, the AI is working. It's hallucinating a little bit, and it's making me a drink, which, I mean, I wouldn't be too upset if I got that drink, but all right now it's being a little bit more reasonable. It gave me espresso and steam milk. We have our cappuccino, all right. So, uh. This was created in X8, and we actually just announced X8 version 5 beta.

[23:00] If you don't know what X it is, traditionally, it's. A state management library for state machines and statecharts. But with X8 version 5 beta, you could actually use pretty much anything, whether it's normal transition functions or promises. And if you thought that actually was, you know, too hard to learn. It's actually a lot simpler than you think, so our simpler, or our simple counter machine, is just this. Basically, we have on increments, updates or sorry, assigned to the count plus one, and on decrement assigned to the count minus one.

[24:00] Then we have a way of subscribing or, yeah, getting the counts.

[24:00] Starting the actor and then sending events. And that's pretty much all you need to know to get started with XState. So I have a minute left. Let's see how far we could get here. We talked about a lot of things mutable versus immutable, effects management, single versus multi-store, direct versus indirect state management. But honestly, my biggest realization is that none of this actually matters. What really matters is three main things first, correctness.

[24:00] How well can you map requirements to the actual code that you write such that your code is correct? And it does the thing that you as a developer expect and the users expect, also velocity, no matter which state management library you choose. How can we actually add features, change features, fix bugs, adjust tests, and onboard new developers as quickly as possible? And this also leads to maintenance. Like, how easy is it for us to document our user requirements, our user flows and everything?

[25:00] And how well can we map that to the state management that actually exists in our code? And so, what is the best state management for app? Logic, really? It is the one that your entire team can understand, and I really want to emphasize, understand. Because state management is not just about the developer updating your code, it is about your entire team understanding what the heck is going on in your code base.

[25:00] And if you're working alone, your team includes you and you two weeks ago, and those are two very different people. Okay, so effective state management, I feel, are, there are four things that are important for this. First of all, map your requirements to code secondly, and this is a maybe a hot topic, but code independently. Of Frameworks, don't try to do something that's baked into the framework code. In a way where if you need to swap out your framework or use no framework at all, then it will still work.

[26:00] Make your interfaces simple, really, no matter how complex your logic gets, you're just reading data and you're sending events, and that's it. And use a common visual language, which it doesn't matter if you're handwriting these diagrams or you're generating them automatically, having these visual diagrams really helps your entire team understand your application logic. So we have two dueling parts we have UI as a function of state, which is part of your framework, and we have state and effects being a function of the previous date and event.

[26:00] Which is part of your state management. And both of these can work together in harmony, where your framework reads the states and your state management. Library or solution handles the state and manages both the updated state and the effects that are part of it. And this Independence can be achieved, honestly, with any state management solution that you use. It's just important to keep these principles in mind and code your application logic in a way that your entire team can understand.

[27:00] So, in general, there are two ways to man state. Both the easy way where you're just getting things done quickly and updating it directly, and the simple way where you're actually making robust application logic. That can really be maintainable and understandable by the entire team. So which one will you choose? Thank you very much.
