the state management decision matrix
Three lightweight alternatives to Redux, three different mental models. Recoil and Jotai rely on atoms as the smallest unit of state, Zustand relies on a single central store. This article compares bundle size, server state integration and maintenance status, so the choice rests on concrete criteria instead of gut feeling.
Table of Contents
- 1. Why these three libraries are compared together
- 2. Core model: atoms versus a central store
- 3. Bundle size and rerender behavior
- 4. Recoil in detail: atoms, selectors and maintenance status
- 5. Jotai in detail: bottom up atoms without boilerplate
- 6. Zustand in detail: one store, minimal API
- 7. Server state integration with TanStack Query
- 8. Migrating between the libraries
- 9. The decision matrix compared directly
- 10. Summary
- 11. FAQ
1. Why these three libraries are compared together
Recoil, Jotai and Zustand all solve the same core problem: sharing React state across component boundaries without the heaviness of classic Redux. All three are noticeably smaller than Redux Toolkit, skip reducer boilerplate and can be wired into an existing project within minutes. Yet they differ fundamentally in their mental model, and that difference is exactly what decides, in practice, which library fits a given project.
Recoil and Jotai belong to the family of atom based state management approaches: state is held in many small, independent units that can be subscribed to individually. Zustand instead follows the more classic model of a central store with a single hook access point, similar to a lean version of Redux without the reducer pattern. Anyone who wants to understand when which model makes sense for their own project needs to first understand the structural differences between atoms and a central store.
2. Core model: atoms versus a central store
An atom in Recoil or Jotai is a single, independent piece of state, comparable to a global version of useState. Components only subscribe to the atoms they actually read, so rerenders are automatically limited to the affected components without any manual selector tuning. In Zustand, all relevant state instead lives in a single store object, and the granularity of rerenders depends on how precisely components access individual fields using selector functions.
This difference has direct consequences for the architecture of larger applications. With atoms, state can grow organically per feature, each new atom is independent and does not need to be added to a central store object. With a central Zustand store, the entire state stays visible in one place, which makes debugging and understanding the overall architecture easier, but can lead to a growing, unwieldy store object in very large applications unless it is deliberately split into multiple stores.
// Recoil atom model — each atom is an independent unit of state
import { atom, useRecoilState } from 'recoil';
const themeState = atom({
key: 'themeState',
default: 'light',
});
function ThemeToggle() {
const [theme, setTheme] = useRecoilState(themeState);
return (
<button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
Current theme: {theme}
</button>
);
}
3. Bundle size and rerender behavior
Bundle size is rarely the decisive criterion for state management libraries, but it matters for applications with strict performance budgets. Zustand is the smallest of the three at roughly one kilobyte gzipped, with no additional peer dependencies beyond React itself. Jotai lands in a similar range, while Recoil is noticeably larger and additionally ships its own context provider plus internal scheduler logic, which visibly grows the bundle.
In terms of rerender behavior, all three differ clearly from plain context based state, where every change potentially rerenders every consumer, thanks to their granular subscription logic. Jotai and Recoil achieve fine grained updates through the atom model itself, Zustand achieves the same result through selector functions passed at store access time. In benchmarks with many independent state slices, Jotai often delivers the fastest results due to its minimal internal overhead, while Recoil carries a bit more overhead per atom update because of its additional bookkeeping for time travel debugging.
4. Recoil in detail: atoms, selectors and maintenance status
Recoil was built by a team at Meta and was the first popular library to bring atoms as a React native concept into the mainstream. Beyond simple atoms, Recoil offers selectors, which declaratively compute derived state from one or more atoms, including automatic memoization and optional asynchronous computation directly inside the selector itself. That makes Recoil particularly powerful for applications with many interdependent derivations.
The decisive downside of Recoil in 2026 is its maintenance status: development has slowed considerably, major releases have become rare, and many teams that previously bet on Recoil are actively migrating to Jotai, which is inspired by the same core ideas but is more actively maintained and offers a smaller, more stable API. For new projects, Recoil is therefore only recommendable in exceptional cases, for example when an extensive selector network already exists that would make migration uneconomical.
// Recoil selector — derived, memoized state computed from one or more atoms
import { selector, useRecoilValue } from 'recoil';
import { atom } from 'recoil';
const cartItemsState = atom({ key: 'cartItemsState', default: [] });
const cartTotalState = selector({
key: 'cartTotalState',
get: ({ get }) => {
const items = get(cartItemsState);
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
});
function CartTotal() {
const total = useRecoilValue(cartTotalState);
return <p>Total: {total.toFixed(2)}</p>;
}
5. Jotai in detail: bottom up atoms without boilerplate
Jotai adopts the atom model from Recoil but deliberately drops the mandatory string key per atom, which means atoms in Jotai need much less boilerplate and are easier to share between modules. Jotai's bottom up approach means: instead of first defining a global store, atoms are created right where they are needed in the code and composed as required, similar to useState, but shareable globally.
A standout feature of Jotai is its native support for asynchronous atoms: an atom can directly return a promise, and React Suspense automatically takes over rendering the loading state, without any extra code for loading flags. Combined with the jotai/utils package, which among other things provides atomWithStorage for automatic LocalStorage persistence, Jotai covers most of the use cases that previously required Recoil or a custom context solution.
// Jotai — async atoms integrate directly with React Suspense
import { atom, useAtomValue } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const userIdAtom = atomWithStorage('userId', null); // persisted automatically
const userAtom = atom(async (get) => {
const id = get(userIdAtom);
if (!id) return null;
const res = await fetch(`/api/users/${id}`);
return res.json();
});
function UserProfile() {
const user = useAtomValue(userAtom); // suspends automatically while pending
return <p>{user?.name ?? 'Guest'}</p>;
}
6. Zustand in detail: one store, minimal API
Zustand drops the atom concept and context providers entirely, a store is defined through a single create function and directly returns a hook that components can import and call. This simplicity is the main reason for Zustand's popularity: no provider wrapping needed in the component tree, no boilerplate, actions and state live in the same object.
Middleware is a central extension concept in Zustand: persist automatically syncs the store with LocalStorage, devtools connects the store to the Redux DevTools for time travel debugging, and immer allows the same direct mutation style as in Redux Toolkit. Because Zustand works outside of React components, the store can also be read and written directly in event handlers, web workers or service classes, without following the rules of hooks.
// Zustand — single store with persist middleware, no provider needed
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useCartStore = create(
persist(
(set, get) => ({
items: [],
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
removeItem: (id) =>
set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}),
{ name: 'cart-storage' } // persisted to localStorage automatically
)
);
function CartBadge() {
const itemCount = useCartStore((state) => state.items.length); // fine grained subscription
return <span>{itemCount}</span>;
}
7. Server state integration with TanStack Query
None of the three libraries ships its own server state caching, which is why in practice TanStack Query is almost always added for data coming from the server, while Recoil, Jotai or Zustand manage only client state. Integration works smoothly with all three technically, because TanStack Query works independently of the chosen client state approach and does not create provider conflicts.
One subtle difference: Jotai's async atoms allow an elegant bridge between both worlds, an atom can internally call queryClient.fetchQuery and expose the result like a normal synchronous value to the rest of the application. With Zustand and Recoil, the separation between server state via TanStack Query hooks and client state via the respective store or atoms usually stays clearer, which for many teams is actually the more understandable architecture.
8. Migrating between the libraries
The most common migration in practice goes from Recoil to Jotai, because both are built on the same atom principle and the API surface can, in many cases, be translated almost one to one. A Recoil atom({ key, default }) becomes a Jotai atom(default), a Recoil selector becomes a derived Jotai atom with a getter function. The bulk of the manual work lies in removing the Recoil root provider component, which is not needed by default with Jotai.
Migrating from Zustand to an atom based approach or the other way around is structurally more involved, because the mental model changes fundamentally: a single store with selector functions has to be broken up into many independent atoms, or conversely many atoms have to be merged into a central store object. Such migrations usually only pay off when concrete performance or maintainability problems have been identified in the current approach, not as a matter of taste alone.
9. The decision matrix compared directly
The following matrix summarizes the practically relevant criteria and makes the recommendation for a concrete project easier to follow than abstract discussions about philosophy.
| Criterion | Recoil | Jotai | Zustand |
|---|---|---|---|
| Bundle size | Largest, own scheduler | Very small | Smallest |
| Maintenance status 2026 | Significantly slowed | Very active | Very active |
| Mental model | Atoms with selectors | Atoms, bottom up, minimal | A central store |
| Async state | Async selectors, complex | Native Suspense integration | Manual in the store |
| Usable outside of React | No, tied to React | Possible to a limited extent | Yes, fully |
| Recommendation for new projects | No longer recommended | For fine grained atoms | For simple global state |
Mironsoft
React architecture, state management and modern frontend infrastructure
Picked the wrong state management library?
We evaluate your current state management approach, map out concrete migration paths from Recoil to Jotai or Zustand, and support the rollout without putting live operations at risk.
Architecture review
Assessment of the existing state management approach against concrete criteria
Recoil migration
Step by step migration from Recoil to Jotai without a big bang rewrite
Zustand setup
Store structure, middleware and TanStack Query integration for new projects
10. Summary
The choice between Recoil, Jotai and Zustand mainly depends on the preferred mental model and current maintenance status. Recoil pioneered the atom concept, but in 2026 is no longer considered the first choice for new projects due to slowed development. Jotai carries the atom idea forward, further reduces boilerplate and offers native Suspense integration for asynchronous values, which makes it the natural choice for fine grained, organically growing state structures.
Zustand drops atoms and providers entirely, offering the smallest bundle size in return, works outside of React as well, and scores with a minimal, easy to learn API. For most new React projects in 2026, the pragmatic recommendation is: Jotai for fine grained, independent units of state, Zustand for a single, clear global store, and in both cases TanStack Query for anything that comes from the server.
Recoil vs Jotai vs Zustand: the essentials at a glance
Recoil
Atoms and selectors, a powerful model, but significantly slowed development. No longer the first choice for new projects.
Jotai
Bottom up atoms without boilerplate, native Suspense integration, very actively maintained. A solid successor for Recoil projects.
Zustand
One store, minimal API, smallest bundle size, works outside of React components too.
Server state
None of the three libraries replaces TanStack Query, all of them combine with it without friction.