Comparing JS Client State Management Libraries
2025-11-08 · 15 min read
Several months ago, I got an opportunity to work on a Wordle game. As I considered which libraries to use, I was paralyzed by the options since I didn’t have a firm preference. Since then, I became increasingly paranoid about the different options, and constantly wondered which I’d use for future projects.
To me, there is a significant factor to consider: How to manage state. I think it matters a lot as state management has a huge influence on how we construct the logic in a frontend application.
So I spent the last couple of months creating and recreating the Wordle game by trying out different state management approaches across libraries and frameworks including Angular, React, and Vue.
For readers who want to see the code, here is the GitHub repo of the project. Feel free to continue reading as I will detail my experience and evaluation of the different state management approaches below.
Why Wordle
Wordle is a popular game that asks the player to guess a word of a certain length (typically a five-letter English word) in a fixed number of attempts (typically six attempts) with color-coded clues.
Many existing state management examples I have seen are to-do lists or tic-tac-toe, which are simple enough for learning. However, I think a new comparison study would benefit from using a different and more challenging app. Wordle happens to be what I was working on and it fits that description.
Wordle demo app in the project.
List of State Managers
Below is a list of the different state managers tried, grouped by the frontend libraries / frameworks they are used in:
- Angular
- NgRx Signal Store
- NgRx Store
- React
- Effector
- Jotai
- MobX
- MobX State Tree
- Redux Toolkit
- Tanstack Store
- Valtio
- XState
- XState Store
- Zustand
- Vue
- Pinia
- Svelte
- Svelte Runes
- Solid
- Solid Store
You may notice that I tried ten different state management libraries for React. I have always wanted to find a good state management solution for React, particularly because React Native is my goto for mobile app development. I don’t see React’s built-in Context API as a state management solution but rather a dependency injection tool. While it can be treated as a state manager, it is not great for that because it can lead to rerendering issues as more states are added. I will explain this in a bit.
On the other hand, for Svelte and Solid, I simply used their built-in store APIs because they are full-fledged state management solutions.
Distinguishing the State Managers
While my evaluation is personal and subjective, I try my best to reason my preferences based on an objective differentiation of the state managers by their key characteristics. Below are several questions that I used to distinguish the different state management solutions:
-
Architectually, does the state manager follow a top-down pattern (e.g. with a store API) or a bottom-up pattern (e.g. with atoms)? Does it require a centralized pattern?
-
In terms of programming paradigm, is it object-oriented (classes), functional (pure functions/reducers), reactive (observables), or declarative (configuration-based)? Does it enforce immutable updates?
-
How fine-grained is the state manager (i.e., the smallest unit of change subscription)? This is influenced by its change detection mechanism. Does it use an observer pattern, proxy-based interception, or observables/signals?
For instance, React Context API as a state manager is a top-down, functional, coarse-grained-by-design solution, and it can be both centralized and distributed. The fact that it is not fine-grained breaks the deal for me as it can lead to a lot of unnecessary rerendering in the context consumers. I call it coarse-grained-by-design because all consumers of a context will still rerender if a value in the context updates even if it does not actually use the value.
My Evaluation
Disclaimer: Below evaluation is filled with my personal and subjective opinions. It is my take on the different state management philosophies, styles, and syntaxes based on my past experience and the direct comparison through the Wordle project.
Angular: NgRx Signal Store
What I like
- Store API is easy to test
- Signal-based which makes it performant & nicely integrates with Angular’s signals
What I don’t like
- Does not support Redux Devtools out of the box
NgRx Signal Store is a signal-based state manager with a store API for Angular. It has a smaller API compared to NgRx Store, has less boilerplate, and is much easier to get started. If I work on a new Angular project that has non-trivial state management, NgRx Signal Store would be my top choice.
Angular: NgRx Store
What I like
- Well-designed Redux store pattern for Angular
What I don’t like
- Has more setup code compared to altnernatives
- Steep learning curve for the Redux pattern & RxJS integration
NgRX Store has been around longer than NgRx Signal Store, and is a battle-tested approach that appeals to enterprises. While I don’t like the boilerplate code, I think it is manageable like Redux Toolkit. The syntax is well designed and seamlessly integrates with Angular and RxJS. For me, NgRx Signal Store is a better chioce for most kinds of Angular projects.
React: Effector
What I like
- Declarative, event-driven pattern with
sample
What I don’t like
- Readability issues with chained and complicated samples
- API allows a mix of styles which can lead to confusion and decision fatigue
Effector is a declarative state manager with three primitives: Event, Store & Effect. It is designed to be declartive because it promotes the use of sample to set up reactions for state update instead of doing so in imperative actions. However, I found that samples become hard to read fast. While the library provides a createAction API for creating actions (see the docs) to mitigate this, it leads to a mix of styles which may cause confusion and decision fatigue.
I also find the createStore API strangely named because the docs actually encourage a bottom-up approach and uses it for creating small units of states (typically called atoms in other libraries).
React: Jotai
What I like
- Very easy and quick to write
- Super lightweight and performant
What I don’t like
- Readability concerns in large-scale apps
Jotai is an atomic, minimalist state management library that I find very easy to pick up and use. As an atomic state manager, Jotai manages states with a bottom-up approach, meaning that we first design and implement small units of states called atoms, and then we use them directly and compose them to create more complicated atoms.
While I enjoy writing Jotai atoms, I have had trouble understanding them in past projects that had many atoms scattered across multiple files. I believe this comprehension issue stems from several factors: a lack of cues about which atoms should be considered together, unhelpful atom names, and poorly written atoms (e.g., duplicates and converting all local state into atoms). Although there are mitigation measures, I think maintaining readability becomes something that one has to consciously work towards as more atoms are added.
React: MobX
What I like
- Decorators are handy and clearly indicate the roles of properties
What I don’t like
- Wrapping components with
observeris a manual step (that is easy to forget)
MobX is one of the better state management libraries that I have tried for the Wordle project. I love the use of classes and class decorators. While the first encapsulates logic of a store, the latter serves two purposes: reducing boilerplate and clearly indicating the roles of the properties decorated.
I also like how easy it is to read and write MobX states once I have accustomed to it. There is one thing that keeps bothering me though, which is the observer component wrapper. To me, it is easy to forget to wrap components which access the MobX store.
React: MobX State Tree
What I like
- It is basically MobX with its own type system and model API
What I don’t like
- TypeScript integration requires workarounds
Mobx State Tree (MST) is an opiniated way of writing MobX with its type API. While I like the syntax and the structure it offers, using it with TypeScript is not a great experience as there are a few issues, such as the use of cast() for object type state assignment and limitations of self (read the official docs for more information). As I use TypeScript most of the time, I prefer the experience of MobX to MST.
React: Redux Toolkit
What I like
- Redux with reduced boilerplate
What I don’t like
- Has more setup code compared to other altnernatives
- Steep learning curve
The reasons for what I like and don’t like about Redux Toolkit are similar to those for NgRx Store. Redux Toolkit succeeds as a simplified version of Redux. It is great for people who like the robustness of the Redux pattern. However, the learning curve, overhead, and boilerplate code are tradeoffs that one has to consider compared to the alternatives.
React: Tanstack Store
What I like
- Minimalist & clean store API with
Store,Derived&Effectclasses
What I don’t like
- There are some inconveniences and limitations when using
Derivedfor computed states:- Explicit mounts and unmounts for
Derived - No custom comparison function option for
Derived
- Explicit mounts and unmounts for
- Documentation is lacking
The API of Tanstack Store is minimalist and clean with just three classes: Store, Derived & Effect. For the Wordle project, I only need to use the first two so I can’t say much about Effect. As for Store & Derived, I quite like them as I find them easy to read and write.
I am not sure whether I should use the Derived API for computed state, but I did so in the project, and I found it inconvenient and error-prone to have to explicitly mount them to make them work. For example, when action B depends on a derived value that runs after action A, you would expect the derived value to recompute synchronously before action B, but it doesn’t if you forget to mount it.
React: Valtio
What I like
- State mutation from anywhere is very convenient
What I don’t like
- One needs to make sure to use proxy states and snapshots for different things. This can easily lead to bugs due to carelessness
- Getters are not equivalent to computed state as they are recalculated on every call
Valtio is a proxy-based state management tool that looked absolutely fantastic to me at first glance, especially in the simple example on the homepage. However, after using it in the Wordle project and encountering a few gotchas and mental hurdles, particularly when deciding when to use proxy states versus snapshots, I was slightly disappointed with the developer experience.
Despite the gotchas, I still think Valtio could be a solid choice if you can get used to it.
React: XState
What I like
- Visual state machine editor is helpful for comprehension and testing
- State machines are well-suited for managing intricate state of specific features
What I don’t like
- Steep learning curve
- Difficult to write
XState is a state management solution that incorporates state machines and the actor model. While I thihk it is very capable, it definitely requires more patience and time to set up compared to the alternatives. In my opinion, it is one of those tools that, given a suitable use case, can be tremendously valuable.
React: XState Store
What I like
- API is designed to be event-driven. It enforces pure transitions over actions for state updates
- Offers an upgrade path to XState’s state machines when needed (see docs)
What I don’t like
- Store API only allows transition definitions, which feels less convenient than actions
- No devtools integration for XState Store yet
XState Store’s store API revolves around two concepts: Context & transitions. Instead of creating and exporting actions from the store, you create transitions which are pure functions that return new contexts. To trigger those transitions, simply use store.send('transition', payload) (although you can still destructure and call the transitions direectly from store.trigger).
Overall, I find it really clean to write and comprehend once I have gotten used to the even-driven mindset.
React: Zustand
What I like
- Minimalist store API
- Middleware is used for adding functionalities to the store
What I don’t like
- Since the library is minimalist, one may need to write some code to achieve certain features
Zustand is a minimalist state management library featuring a store API and immutable state model. I personally like how Zustand treats stores as hooks via the create function as it just blends in with React. The fact that it is unopinionated and allows the use of middleware for enhancement makes it really versatile.
Given its minimalist and unopinionated nature, Zustand is not for those who want something that just works out of the box with a ton of features built-in.
Vue: Pinia
What I like
- It just uses Vue’s reactivity system and Composition API
- Easy to read and write
What I don’t like
- As Vue’s reactivity system is proxy based and Pinia is built on top of that, similar to props in Vue, you need to avoid destructuring a
storebecause the resulting destructured variables are not reactive (but Vue developers are already used to it)
Pinia is the official state management library for Vue. It is easily one of the best state management libraries I have used (I am clearly biased). Its store API seamlessly integrates with Vue’s reactivity system (e.g. ref & computed). Its plugin API is powerful and convenient as you can install and use a community’s plugin like pinia-plugin-persistedstate to add extra behavior to your store.
Svelte
What I like
- Runes like
$stateand$derivedare so easy to use and powerful - You can just use runes in a class as a store
What I don’t like
- No devtools yet for Svelte 5 (see Github issue)
Svelte runes are very intuitive and work like a charm for state management when used in classes as stores. Surprisingly, I find myself enjoying writing Svelte states the most among all the state management approaches I have tried for the Wordle project.
Solid
What I like
createStorefunction is for creating a store for the base state, which integrates with existingcreateMemois used for derived states
What I don’t like
- Separating stores from signals is a little confusing to me. I personally prefer how Vue Pinia approaches this: Refs are used for making state values, and the store is just a wrapper around the refs and computes.
Solid’s createStore creates a simple yet powerful, proxy-based state management primitive store that seamlessly integrates with the existing primitives like memos by createMemo. Stores are said to be unlike signals created by createSignal as the former maintains fine-grained reactivity by updating only the properties that change. I personally find it a little confusing as to why and when should someone use createStore over createSignal.
Final Thoughts & Tierlist
Having tried the different state management approaches in the Wordle project, I don’t think there is clearly a single better state management approach because each has its own benefits and tradeoffs. That said, I find myself enjoy using Vue’s Pinia and Svelte Runes the most, and I am not even a Svelte developer as this was my first time using Svelte 5.
If I were to rank them based on my own preferences, which mainly revolve around developer experience, such as how easy it is to read and write code as a solo developer, this would be the tierlist I would come up with.
| Tier | Libraries (in no particular order) |
|---|---|
| S | Svelte Runes, Vue Pinia |
| A+ | Angular NgRx Signal Store, React MobX |
| A | Solid Store, React Jotai, React XState Store, React Zustand |
| B | React XState, Angular NgRx Store, React Redux Toolkit, React MobX State Tree, React Valtio, React Effector, React Tanstack Store |
It may be a hot take, but it turns out that I actually like MobX the most among all state management libraries for React. The class decorators are an absolute joy to use. I can see how people who prefer functions in React argue that classes do not match React’s styles. However, I think MobX’s classes and decorators really work well for client state management because of the logic encapsulation and because the decorators describe the roles and provide mechanisms to make them work: @observable, @computed and @action.