The two types of state management
Watch on YouTube (React Finland)
What is this talk about?
All state management libraries and techniques can be categorized in two ways. One is easy, one is simple. One is quick, one is maintainable. From state mutation to statecharts, let's explore the various ways developers manage state and learn how we can choose the best strategy for efficient, robust state state management for different use-cases, and discover what these two categories are. (From the video description.)
“My goal here today is to help you choose the best state management.”
“If you've ever done any sort of state management with context before, you're going to get bitten by the fact that components are going to re-render unnecessarily.”
From the opening of the talk.
Key takeaways
- My goal here today is to help you choose the best state management. 1:00
- If you've ever done any sort of state management with context before, you're going to get bitten by the fact that components are going to re-render unnecessarily. 7:00
- Different state management libraries have different ways of managing effects, but the other important thing I want to tell you is that effect management is state management. 15:00
- You could map your requirements to code like this is what I would suggest for whatever state management solution you choose, basically, have a very clear mapping of the user stories. 21: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,939 words.
0:00 Introduction
Hello everyone. So I was going to talk about useEffect, like I've been talking for the last few months. But if I just installed react to produce stress, then, you know, I wouldn't have had to talk about it at all. Use the fact is terrible, don't use it. That's basically all of my talks, but now we're going to talk about two types of state Managements, and honestly, useEffect is not that terrible. So, my name is David Khourshid, I'm at Stephen key piano pretty much everywhere online. And, like Sarah said, I'm the founder of Stately..ai, where we think about state management all the time. So, there's a lot of choices for state management out there today. Like Emer, Redux, X Tate, which I maintain, MOBX, Joe Tai Recoils, Duston, RxJS. The list goes on and on. There's so many choices. So my goal here today is to help you choose the best state management. And I promise I'm not biased at all, even though I created XState.
So, what? We're going to talk about this and I'm going to help you make a decision based on your needs, right? So, first of all, why do we have state management in the first place? And the reason is because before we had Singletons and we had Global Mutable states, and we all decided that Global state. And Mutable state is just a really bad thing. But have you thought about why it's a bad thing? Well, the thing is that Mutable state isn't actually a bad thing, not at all. In fact, none of our apps would work if we didn't have. Mutable states. It's shared mutable state, that's bad, and the reason it's bad is because with shared mutable state, we have accidental complexity leaking in. It's because when you have something that could be mutated from multiple sources now you don't know what is updating, what. You can't isolate your logic, so your code becomes very hard to understand, and it becomes very messy and your tests fail for no reason.
So, and also on a technical aspect, React works within Edible seat. Because it's using referential checking to see should I update this component or this part of the component, like, basically, should I do a re-render? And so when you have mutable state, the references are the same reacts can say, Oh, okay, yeah, it's the same object, and so that's a problem, so getting that out of the way. Shared mutable state is the root of all evil. And this is what causes accidental complexity in our code base, so, again, you know, mainly because of reacts, but also because for developers, it makes it really hard to understand our code. So, we could actually experience a lot of these problems with accidental complexity. Even when we adopt better state management strategies, so, all of these state management libraries, including, you know, just React itself, if you're using, the normal state.
3:00 We have you state, you know…
Hooks are subject to this complexity. And they love to use counter examples, because that's the simplest thing you could do, right? So, let's start with the use date hook. This is a very simple counter example. And if you are a lucky developer working for a company that all they do is make counters, then you're set. You could just use Reactokes, you could useState all day long. So we have you state, you know, we have set counts where we could increment the counts and decrement the counts, and this is as simple as you could get in. React. So why do we need state management solutions? Why do we need any of the other hooks? Well, it's because our requirements are never as simple as this. Let's say that we added a very simple requirements where we wanted to only counts between 0 and 10.. Okay, so now we have to add a few things. And because I put my logic in the event handler, I'm just going to put the, the conditions right inside of there.
It still doesn't look too bad. I could still fit it on one line unless yells at me, and then I have to make it three lines. Because I need those curly braces for no reason whatsoever. But now there's, there's a problem, because, you know, I, I might want to increment it and decrement it in other places. So it's a good thing to just move these into callbacks up here. And, you know, if I'm super, super, you know. Cautious about performance, I will wrap these and useMemo, thinking that it will give me better performance. Spoiler alert it will not. Or at least it will like a very minuscule amount, and you're going to increase your memory usage so performance or balances itself out there. And also look down here. Some developer on my team decided to add an input where I could change the state whenever I want on blur. So when I increment, sure, I won't be able to go above 10, when I decrement, I won't be able to go below zero.
But now, you know, if you just type any number in here, you, you could still go above 10, below zero. And yeah, so let's fix that problem. We could just put everything into change counts, and I forgot to highlight these. But now when we change counts, we are ensuring that we're only setting the counts to a value between 0 and 10.. So did this solve our problem? Not really, because we still have this set count variable available. And if you take it out of there, then, you know, another developer might just add it in. So you're still culpable of, you know, just introducing impossible states in your app. So, let's get a little bit smarter about this very simple counter example, and let's actually use a reducer. You know, that big nasty hook that we sort of as chew for use date. Which is actually really useful because it does give meaning to, how we could update our state.
So now I could clearly see there's three operations I could do, I could increment, decrement, set my state. And I could also ensure with this cool little math.math.max that it stays between 0 and 10., no matter how high or low of a number I set. And then over here, all I have to do is send and use an event object and just send it over. By the way, if you didn't know about e.Target.value as number, really, really useful, stop trying to cast your strings as numbers from events, you could just use valueless number. It's extremely useful, all right, so useReducer, it is useful, but now let's say that we want to share the state with multiple components. So we would use context, so we would first create a context and then we could read the counts from this context. Because we're passing it into a context. Provider in the value down. Here, okay, simple enough, you know? We just hoist this to the top of our app so that we could use it in our components.
And now we actually don't have a way to send events to it though, so we can't really increment or decrement, so that's a little bit of a problem. Okay, so let's, let's bring that in, too. So now I'm also passing in send, so I actually have that send along with the counts, so what's the problem here? I mean, we have counts, we have send, we could share with all of our components. Well, if you've ever done any sort of state management with context before, you're going to get bitten by the fact that components are going to re-render unnecessarily. So instead, we're going to set up some sort of subscription because we don't actually want our components to re-render. We want to subscribe to the values from the components that actually care about the counts. So we're going to have some sort of store that we make, we could subscribe to that store.
And we still have our send function, so we could send events to the store, and we're also keeping the state up to date just by subscribing to that store. And whenever we get accounts, we're back to our good old friend. You states in our components, so this ensures that only the component that cares about the counts actually re-renders, which is great. But then there's another problem. And that's, you know, let's say that we have a component that only cares about even numbers, or only cares about if you're at the lowest or the highest value. So instead, we need to have some sort of selector mechanism because we want our components only to re-render. When the specific states or derivation of state that we need in the components, you know, is what we expect. So, right now I'm just reading counts. But you can imagine this transforming to whether it's even or odd, or Min or Max, or something like that. And you know, we have a small little U selector hook.
That's just making sure that we are only saving the state of, you know, the whatever we're selecting.
9:00 Manipulate the state that way…
And so with all that's, we've solved our problems and we've recreated Redux, you know, the thing that we. Tried to, move away from, so that's, uh. That's one of the problems of state management when you're using hooks. And so I'm sure that anyone who's worked on an application complex enough knows that hooks are really not sufficient for more complex. And just, I'm not talking about, like, really complex state management, but anything more than a trivial level of seat management. And so that's why we have a whole bunch of state management libraries, and I've separated them into two rough categories. So some are used date-like, where we're directly manipulating states, and some are useReducer-like, where we're indirectly manipulating states by dispatching events or actions. As it's called, you know, in Redux, but let's let's simplify this even further. So, instead of it being used in the useReducer, like, we have direct and indirect ways of managing this date.
So let's go through a few libraries and see what the landscape looks like today. First, we have recoil, where we have this notion of atoms, and so this is one of the direct ways of updating states. You would just, you know, set text, you use recoil state, so it feels very much like use states, and you could just manipulate it from everywhere. There's also a vault here, which is, not really exactly the same. You have this, you know, global-ish states. You could use the snapshot of the state, and you could directly manipulate that state over here. It's using, a proxy to just, maintain observers in the components. So that it's only going to re-render when, you know, for the things that are actually reading the states, uh. There's also MobX, which, you know, has been around for a while, and so with MobX, it does have the notion of these actions like increased timer.
But at the same time, you could still just directly manipulate the state, and it's going to cause your component to re-render. When the state updates, so we could see that. Down here. We have my timer dot increase timer, which can feel like an event, but again, you could just directly manipulate the state two and get the same effects. And there's also jotai or yotai, I'm not sure how it's pronounced, but this is very similar to recoil, where you have. Atoms, and you could just read the atom and just, you know, of course, manipulate it from anywhere you have this set text, for instance, so it feels very much like recoil. And so these are the direct state manipulation libraries, or at least some of the more popular ones that I've seen, so now, indirect, event based. There's a few options to choose from. There's Redux, and the example is actually pretty long, not as long as I thought.
There's Redux Toolkit, which actually makes your old school Redux a lot simpler, and I do recommend using Redux Toolkit if you're coming from Redux. You know, you have your reducers, your slices, and you would send events. So you grab this patch from, use dispatch and then and highlight it here. But you just dispatch events in the state updates and you could read the state using selectors. There's also Zastan Zushdant. I'm terrible at pronouncing state management names, but it is sort of like Redux, where instead of just having different actions, you have these functions over here and you read set. And then you could just, you know, set the state within those actions. And you also have, I guess, a selector mechanism over here, these create a hook, which is pretty cool. So you have used Bear store? And then you could just read the values that you care about, and your components will re-render. You know, when that value changes. And so you could, just, grab the, grab the accent in the same way that you read the states.
And, just, you know. Manipulate the state that way, it's still an indirect way of manipulating the state, because I think you can't really like, directly. Change, for example, the number of fares. You have to either click, increase population or remove. All bears, which sounds very Grim. Bears aren't that bad. And of course, there's also XState, so exit, you create this machine definition, and honestly, like, you don't need to think in finite states, in nested states, parallel, whatever, you could use XState pretty much the same as you would use Distend or Redux. Where you have this context with data, and then you have these, you know, these transitions, where you could assign to context, so you know, you could add one, subtract. One, and then you basically have the same mechanisms where, just like useReducer and Redux and Zustin, you could indirectly manipulate the state by sending events. So, there's also local versus global. But all of the libraries I mentioned are Global capable, which means that you could use them in a global context.
And basically read the state. And, you know, manipulate the state from anywhere in your React tree. And if you wanted a more local solution, then that's where you state and useReducer would come in handy. There's also multi-store versus single store, and I know that Redux insertion may be at maybe, the decision to have only a single store just because it's, I guess, easier to implement. You have this single source of truth, whereas you have multi-store solutions, including XState, where you could have multiple stores. Which means that you could use these bits of state at any level of your application, and you could have different concerns for different parts of your app. Whereas with Redux insertion, you have this idea of slices where, you know, different slices would pertain to different responsibilities in your application. So, you know, they sort of more adhere to the single source of truth. But one important thing, and this is, you know, I'm going to mention a few important things in this talk.
But one important thing that I want you to know is that single source of truth is actually a lie. There is no single source of truth in your app. And the reason is because data your state lives everywhere, and so, you know, you have to just be able to read this data from everywhere. You have different things that you're communicating with, whether it's the DOM, whether it's some GraphQL API, whether it's some separate service or some separate library. There, you can't assume a single source of truth. There's also effect management, which I'm not going to get into. Different state management libraries have different ways of managing effects, but the other important thing I want to tell you is that effect management is state management. So instead of just thinking in terms of when I have an event that comes in, this is the next state that we get. It's important to think about what are the effects that happen.
As a result of my state transition, or as a result of the state changing due to the event? There's also two types of performance optimizations that libraries typically do. Whether it's selectors, which I consider a manual approach, doesn't mean bad, it's just manual and observers, which is more of an automatic, magical approach. So that's another consideration to keep in mind. So, you know, I have less than 10 minutes left, so I'm going to summarize a little bit. We've talked about a few things directs versus indirect, single store versus multi-store, effect management, performance. But none of this actually matters. And and these aren't really the things that I want you to think about when choosing a state management solution. So what are the important things? Well, the first thing is correctness. You want to make sure that your application logic is bug free, accessible, doesn't have race conditions, adheres to specifications, and is verifiable.
Which means whatever user stories you're handed from your PM, does the logic actually match that? And is it going to introduce bugs or prevent bugs?
17:00 We have simple ways where, you know…
Also, velocity, whatever solution you choose, are you able to add, change, remove features quickly without removing bugs. Are you able to onboard your team quickly so that they themselves can add features and make changes rapidly? And also maintenance. Like, how well can you map your application logic to documentation performance? How could you, you know, better, test your application logic? Do you have a single source? Where you could just isolate your logic? And actually, unit test it, integration test it, and all of that? So those are the three things. And so that's why, you know, I haven't really talked about state machines yet, but I. One of the reasons that I love state machines is because state machines allow you to express both the specification like this user story over here and allow you to go more into detail and refine the parts of your state machine with actual implementation logic.
And so, you know, that's why I created XState. And so that's why, in helping you deciding which state management is best for representing these kinds of flows, the real answer is the one that your team is the most comfortable with, right? I, I don't mean to say that XCT answers. You know, not, but like, we've been working on XState, and we have version five Alpha, that's, you know, hopefully coming out soon. And so we've been thinking about these problems too. And, uh. One of the approaches that we're going to take is making X8 version 5 a lot simpler to onboard and also a lot more flexible. So that you could interpret more than just steep machines, like promises, observables, reducers and callbacks. And this has the benefit of you being able to visualize your entire application, architecture, like just how each of these parts talk to each other and even generate more visuals, like sequence diagrams.
And also XState works with a bunch of different libraries, too. And this is one of the reasons why I've been thinking about these problems a lot. And you don't really have to choose between different state management libraries, because XCX more like a state orchestrator rather than state management. With all these libraries and two mystery ones that you're not, I'm going to see because conference Wi-Fi sucks. So in general, the way that we typically code applications is the easy way, you know, we have logic and event handlers, callbacks and ad-hoc logic just floating around. We have simple ways where, you know, we would just separate our application logic into reducers and maybe combine those reducers. And this allows us to have a simpler understanding of how everything works, because everything is just in events. And then we had, you know, a simpler way where we could better organize the logic within our reducers. But of course, this isn't really easy. It is a learning curve to use state machines, and, when your app gets really complex, that's when statecharts come in, and that's definitely not easy.
20:00 To code, code in a view agnostic way…
So, overall, it doesn't matter like what you use. What I really want to emphasize is that your application logic and the logic, your view logic are not always the same. In other words, the data flow tree is not the same as your UI tree. And it's just really good to keep those two things separate, so this separation can be achieved really, with any state management library. Basically, you're separating your user interface from your store, and so you would send events to your store saying the user did this thing, or. This thing happened, and then from your store, you're just reading state, and that's it. I mean, there could be a lot of complexity in the user interface, there could be a lot of complexity in the store. But you could save yourself and your team from failing to understand the code by separating those parts of complexity and having a very clean way of communicating between the two. So, you could map your requirements to code like this is what I would suggest for whatever state management solution you choose, basically, have a very clear mapping of the user stories.
To code, code in a view agnostic way, even if you don't plan. If you don't plan on changing state management libraries or changing Frameworks or anything, just code in a view agnostic way. It really helps you enforce that separation, make your views dumb. We've heard about smart and dumb components, make all of your components done, because your components should just be things. That read state. And that send intentions from the user to some store or stores, whether you're using single or multi-store. And then literally profit, because what I mean by profit, you know? And I'm thinking about this, especially as a founder of a startup, is that the important parts of code are not really just how fast it is. How much ES Lints likes our code, whether it's in TypeScript or not. But it's really the value that we're providing users. Are we able to ship features fast, are we able to create intuitive user interfaces, and are we able to maintain the code?
So it doesn't take forever to add new features? And this works with any state management solution. So, of course, there's a bunch of important things, but I feel like these things are important on the right hand side. And overall, just make it work, make it right and make it fast. And, you know, you're just going to have a better time, you know, just managing your state. So, in summary, there's two types of state management. There's simple state management where your code becomes very easy to understand no matter how complex it gets, and there's easy state management where your code is very quick for you to get up and running. You know, just putting the logic in there, manipulating. State wherever you want, without any care of how it maps to application logic. So which one will you choose? Thank you, React inland [Applause].
Related talks
-
Goodbye, useState
G2i, 2025. 23:58, 50.8K views -
State Management at Scale in React & Next.js
Frontend Masters course preview, State Management at Scale in React & Next.js, 2025. 14:56, 3K views -
Goodbye, useState (JavaScriptMN)
JavaScriptMN meetup (June 2025). 34:30 -
Goodbye, useEffect
Reactathon 2022. 26:00, 240.2K views