|
| 1 | +--- |
| 2 | +title: 'Minimal state management tools' |
| 3 | +date: '2025-03-05' |
| 4 | +tags: ['JavaScript'] |
| 5 | +images: ['/articles/minimal-state-management-tools/coding.jpg'] |
| 6 | +summary: "Modern web applications require effective state management solutions, but choosing between Redux, Zustand, Nano Stores, Context API, Recoil, Jotai, and other options can be overwhelming. Let's compare some of these state management tools based on complexity, performance, developer experience, and use cases to help you select the right one for your specific project needs" |
| 7 | +authors: ['audrey-behiels'] |
| 8 | +theme: 'blue' |
| 9 | +--- |
| 10 | + |
| 11 | +In this article, I will talk about four state management tools: Zustand, Nano Stores, Jotai, and MobX. Before diving into these specific tools, it's crucial to understand two fundamental state management concepts: the pull-based and push-based models. |
| 12 | +The pull-bassed model is used when the application "pulls" the data when needed. This is the traditional approach of many developers. Examples of pull-based patterns include: Functions, promises, callbacks, and event-listeners. Whereas in a push-based model, the data updates get automatically "pushed" to the components that use the state. Examples of push-based patterns include: Observables and streams. |
| 13 | + |
| 14 | +Now that we've clarified these foundational concepts, let's explore the state management tools I've selected for discussion, starting with Zustand. |
| 15 | + |
| 16 | +## Zustand |
| 17 | + |
| 18 | +[Zustand](https://zustand.docs.pmnd.rs/getting-started/introduction), which fittingly means "state" in German, is a small, fast, and scalable state management library designed for React, but can be used in any Javascript environment. Created six years ago, and is now on their fifth version, Zustand offers developers a lightweight alternative to more complex state management solutions. |
| 19 | + |
| 20 | +At its core, Zustand is designed with simplicity in mind. It provides a comfortable API based on hooks, leveraging React's own hook system. |
| 21 | +This library's ease of learning and minimal setup makes it an attractive option for developers looking to integrate it into their projects. |
| 22 | +The tool is excellent for small to medium-sized projects but can be used in larger applications with good architecture. |
| 23 | + |
| 24 | +It runs on Node.js and supports TypeScript, enabling developers to define types for their state and actions, which enhances code reliability and maintainability. |
| 25 | + |
| 26 | +Unlike some other state management tools, Zustand doesn't require wrapping your entire application in a provider. This simplifies the setup process and makes it easier to introduce state management incrementally into existing projects. |
| 27 | + |
| 28 | +Zustand uses a pull-based model. It allows for efficient state management through the use of selectors, enabling components to subscribe only to the state they need. |
| 29 | +In Zustand, a state can be updated without the use of dispatched actions and reducers. Actions can be added to the store. This has a few advantages: it doesn't require a hook to call an action and it facilitates code splitting. |
| 30 | + |
| 31 | +The library supports middleware as well and can handle asynchronous operations without the need for additional middleware or complex setups. |
| 32 | +Zustand is often used with [React Query](https://tanstack.com/query/v3/) or [SWR](https://swr.vercel.app/) for data fetching, providing a comprehensive solution for both state management and data fetching needs. |
| 33 | + |
| 34 | +### Code examples |
| 35 | + |
| 36 | +Defining a store, with values and actions that update the store: |
| 37 | + |
| 38 | +```js |
| 39 | +import { create } from 'zustand' |
| 40 | + |
| 41 | +const useStore = create((set) => ({ |
| 42 | + bears: 0, |
| 43 | + increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), |
| 44 | + removeAllBears: () => set({ bears: 0 }), |
| 45 | + updateBears: (newBears) => set({ bears: newBears }), |
| 46 | +})) |
| 47 | +``` |
| 48 | + |
| 49 | +Rendering data from your store, this is the same for the actions: |
| 50 | + |
| 51 | +```js |
| 52 | +function BearCounter() { |
| 53 | + const bears = useStore((state) => state.bears) |
| 54 | + return <h1>{bears} around here...</h1> |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +## Nanostores |
| 59 | + |
| 60 | +[Nano Stores](https://github.com/nanostores/nanostores) is a tiny state management tool that can be used with React, React Native, Preact, Vue, Svelte, Solid, Lit, Angular, and vanilla JS. Four years ago Nano Stores was created. The library is designed to move logic from components to stores. It's known for its lightweight nature and good performance in various scenarios and well-suited for projects of medium size. |
| 61 | + |
| 62 | +The library uses many atomic stores and direct manipulation. Nano Stores has a straightforward API, resulting in a low learning curve for developers. Unlike some more complex state management solutions, Nano Stores doesn't require extensive boilerplate code, allowing developers to get up and running quickly. |
| 63 | + |
| 64 | +The library adopts a push-based model for state updates. This approach ensures that components are immediately notified of relevant state changes, leading to more efficient updates. |
| 65 | + |
| 66 | +Server-side rendering is supported, making it suitable for applications that require this capability. TypeScipt is supported as well, enabling type-safe state management out of the box. |
| 67 | + |
| 68 | +While Nano Stores lacks a formal middleware system, it offers alternative features and patterns that enable developers to achieve similar results. |
| 69 | + |
| 70 | +### Code example |
| 71 | + |
| 72 | +Defining an atom, updating the value: |
| 73 | + |
| 74 | +```js |
| 75 | +import { atom } from 'nanostores' |
| 76 | + |
| 77 | +const counter = atom(0) |
| 78 | +counter.get() |
| 79 | +counter.set(counter.get() + 1) |
| 80 | +``` |
| 81 | + |
| 82 | +Defining a map, updating it: |
| 83 | + |
| 84 | +```js |
| 85 | +import { map } from 'nanostores' |
| 86 | + |
| 87 | +const profile = map({ name: 'anonymous' }) |
| 88 | +profile.setKey('name', 'Kazimir Malevich') |
| 89 | +``` |
| 90 | + |
| 91 | +Defining a deep map and updating this: |
| 92 | + |
| 93 | +```js |
| 94 | +import { deepMap } from 'nanostores' |
| 95 | + |
| 96 | +const profile = deepMap({ |
| 97 | + hobbies: [ |
| 98 | + { |
| 99 | + name: 'woodworking', |
| 100 | + friends: [{ id: 123, name: 'Ron Swanson' }], |
| 101 | + }, |
| 102 | + ], |
| 103 | +}) |
| 104 | +profile.setKey('hobbies[0].name', 'Scrapbooking') |
| 105 | +``` |
| 106 | + |
| 107 | +## Jotai |
| 108 | + |
| 109 | +[Jotai](https://jotai.org/), meaning "atomic" in Japanese, is a state management library for React, inspired by Recoil. Launched five years ago and now is on its second major version. The library takes an atomic approach to global React state management. |
| 110 | + |
| 111 | +While primarily designed for React, Jotai is compatible with various frameworks including Next.js, Waku, Remix, and React Native. The library stands out for its minimal boilerplate and moderate learning curve, making it scalable in a project of different sizes. |
| 112 | + |
| 113 | +Jotai makes use of hooks and allows atoms to be created and used without wrapping your application in a provider. The library uses a pull-based model for state updates and comes with built-in utilities. It has excellent TypesScript support. The library has extensions so you can use others, like React query for data fetching and XState for complex state management. Jotai also offers server-side rendering support when used with frameworks like Next.js and Waku. |
| 114 | + |
| 115 | +### Code examples |
| 116 | + |
| 117 | +Defining an primitive atom: |
| 118 | + |
| 119 | +```js |
| 120 | +import { atom } from 'jotai' |
| 121 | + |
| 122 | +// single value |
| 123 | +const countAtom = atom(0) |
| 124 | + |
| 125 | +const [count, setCount] = useAtom(countAtom) |
| 126 | +// Array |
| 127 | +const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka']) |
| 128 | +// Object |
| 129 | +const productAtom = atom({ id: 12, name: 'good stuff' }) |
| 130 | +``` |
| 131 | + |
| 132 | +Defining an Derived atom: |
| 133 | + |
| 134 | +```js |
| 135 | +import { atom } from 'jotai' |
| 136 | + |
| 137 | +const progressAtom = atom((get) => { |
| 138 | + const anime = get(animeAtom) |
| 139 | + return anime.filter((item) => item.watched).length / anime.length |
| 140 | +}) |
| 141 | +``` |
| 142 | + |
| 143 | +## MobX |
| 144 | + |
| 145 | +[MobX](https://mobx.js.org/README.html) is a signal-based, battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming. Their strategy is "Anything that can be derived from the application state, should be. Automatically.". |
| 146 | + |
| 147 | +The library uses a virtual dependency tree to track which parts of the state are used by which reactions, ensuring that only relevant parts of an application update when state changes occur. |
| 148 | + |
| 149 | +Built around the concept of observables, MobX works well with object-oriented programming, allowing developers to work with classes and objects in a way that's natural to OOP principles. It employs a push-based model where changes in observables automatically trigger reactions and computations. The library's main concepts include observables for representing the state, actions for modifying the state, reactions for synchronizing state changes with effects like UI updates, and computed values for automatically deriving values from the state, also called derivations. |
| 150 | + |
| 151 | +Created nine years ago and is currently on version 6.13.6. MobX works in any ES5 environment, which includes browsers and NodeJS. It has excellent TypeScript support. |
| 152 | +While MobX can be used as a standalone library, most people use it with React. However, its flexibility allows for seamless integration with other popular frameworks such as Vue and Angular as well as plain vanilla JavaScript applications. |
| 153 | + |
| 154 | +There is a higher learning curve compared to other libraries, especially for developers new to reactive programming concepts. |
| 155 | + |
| 156 | +### Code examples |
| 157 | + |
| 158 | +Class component: |
| 159 | + |
| 160 | +```js |
| 161 | +import { makeAutoObservable } from 'mobx' |
| 162 | + |
| 163 | +class TodoStore { |
| 164 | + todos = [] |
| 165 | + |
| 166 | + constructor() { |
| 167 | + makeAutoObservable(this) |
| 168 | + } |
| 169 | + |
| 170 | + addTodo(text) { |
| 171 | + this.todos.push({ id: Date.now(), text, completed: false }) |
| 172 | + } |
| 173 | + |
| 174 | + toggleTodo(id) { |
| 175 | + const todo = this.todos.find((todo) => todo.id === id) |
| 176 | + if (todo) { |
| 177 | + todo.completed = !todo.completed |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + get incompleteTodosCount() { |
| 182 | + return this.todos.filter((todo) => !todo.completed).length |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +export const todoStore = new TodoStore() |
| 187 | +``` |
| 188 | + |
| 189 | +Functional component: |
| 190 | + |
| 191 | +```js |
| 192 | +import { makeAutoObservable } from 'mobx' |
| 193 | + |
| 194 | +function createDoubler(value) { |
| 195 | + return makeAutoObservable({ |
| 196 | + value, |
| 197 | + get double() { |
| 198 | + return this.value * 2 |
| 199 | + }, |
| 200 | + increment() { |
| 201 | + this.value++ |
| 202 | + }, |
| 203 | + }) |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +## Comparison |
| 208 | + |
| 209 | +<div class="table-wrapper" markdown="block" style={{ overflowX: 'auto' }}> |
| 210 | + |
| 211 | +| | Zustand | Nano stores | Jotai | MobX | |
| 212 | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------- | |
| 213 | +| Complexity & learning curve | Easy to learn, low learning curve. <br/>Straightforward api<br/>Hook based | Low learning curve.<br />Straightforward API<br/>Atomic based | Medium learning curve.<br/> Atomic based | Has a higher learning curve. | |
| 214 | +| Set-up & boilerplate | Minimal setup, less boilerplate | No complex boilerplate | Minimal boilerplate | Boilerplate-free. More verbose set-up. | |
| 215 | +| Performance | It's designed to be fast and lightweight | Known for its lightweight nature and good performance in various scenarios | Very good performance | Excellent for complex, frequently updating state due to its fine-grained reactivity system | |
| 216 | +| Tooling | React focused. Integrates well with existing React tools and libraries. | Multi framework | React focused | Multi framework, you can use MobX with other frameworks like Flutter/dart, lit, Anguar, vue. | |
| 217 | +| Scalability | Excellent for small to medium-sized projects, but can be used in lager applications with good architecture | Suited for medium projects | Good, suitable for small to medium-sized projects | Works good for larger application with more complexity | |
| 218 | +| Middleware | It supports middleware. Often used with React Query or SWR for data fetching | No formal middleware, it provides several feature to achieve similar functionality to middleware, like store events. | Has it own set of utilities and integrations | It is supported. | |
| 219 | +| Server-side rendering support | Yes | Yes | Yes | Yes | |
| 220 | +| Pull-based vs. Push-based model | Pull-based model | Push-based model | Pull-based model | Push-based model | |
| 221 | +| Typescript support | Yes, good | Yes, good | Yes, excellent | Yes, excellent | |
| 222 | +| Bundle size | Small, install size 87.1 kB | Install size 42.6 kB | Install size is 486 kB | Install size is 4.13 MB | |
| 223 | +| Community & support | Smaller but growing. Almost daily commits on the repo | Small ecosystem | Growing | Large ecosystem and active community | |
| 224 | + |
| 225 | +</div> |
| 226 | + |
| 227 | +## Conclusion |
| 228 | + |
| 229 | +Each library has its strengths and is suited for different scenarios. Zustand and Jotai are gaining popularity in the React ecosystem for their simplicity and effectiveness. MobX remains a powerful choice for complex applications. Nano Stores offers a unique, lightweight approach that can be particularly useful in multi-framework environments or when bundle size is a critical concern. |
| 230 | + |
| 231 | +For simple, lightweight state management in React, consider Zustand or Nanostores. |
| 232 | +Jotai is a great choice for flexible, atom-based state management in React. |
| 233 | +For complex applications with intricate state interactions, especially in OOP style, MobX is very powerful. |
| 234 | + |
| 235 | +The choice between these libraries often depends on the specific needs of the project, the team's familiarity with state management tools, and the desired balance between simplicity, power, and performance. |
| 236 | + |
| 237 | +I'd be interested to collaborate on a project that uses Zustand for state management. Its comprehensive documentation includes practical examples, making implementation straightforward. The library offers excellent tutorials for newcomers, and having React experience is definitely beneficial when working with Zustand, as it's designed specifically for React applications. |
| 238 | +My thoughts on Jotai are quit similar, I appreciate the readable documentation. I particularly like their interactive playground where you can experiment with the code directly without needing to set up a project from scratch. |
| 239 | +What impresses me about Nano Stores is its cross-framework compatibility and significantly smaller footprint compared to other state management solutions. |
| 240 | +The documentation for MobX gives the impression of being more technically demanding to understand compared to other libraries. |
| 241 | + |
| 242 | +## Links |
| 243 | + |
| 244 | +[Zustand, When, how and why](https://dev.to/ricardogesteves/zustand-when-how-and-why-1kpi) <br/> |
| 245 | +[State Management in Astro: A Deep Dive into Nanostores](https://meirjc.hashnode.dev/state-management-in-astro-a-deep-dive-into-nanostores)<br /> |
| 246 | +[State Management with Jotai — React and TypeScript ready library](https://medium.com/@maciejpoppek/state-management-with-jotai-react-and-typescript-ready-library-a40ac967ac3e) <br/> |
| 247 | +[MobX adoption guide: Overview, examples, and alternatives](https://blog.logrocket.com/mobx-adoption-guide/#what-is-mobx) |
0 commit comments