State Management in React: Zustand and Jotai
"TLDR: This article explores different approaches to state management in React applications, with a focus on two global state management tools: Zustand and Jotai. It first analyzes the limitations of using useState and useContext for state management, including performance issues and complex component update logic. Next, it provides a detailed introduction to Zustand's centralized state management model and its advantages, such as fine-grained control over state updates. Then, it discusses Jotai's distributed atomic (Atom) model and its benefits in improving rendering performance. Finally, the author recommends using Zustand or Jotai in large applications to simplify state management code and improve efficiency."
Just now I was pushing Gemini 3 to work again, revamping another frontend UI design-focused product to see if I could turn it into my own product and then promote it.
During the downtime while Gemini 3 was working, I noticed the project uses Zustand for state management. Although I've always gone all-in on useState for state management, and sometimes used Jotai, I've never really dug deep into these advanced state management tools.
State in React
We know that UI rendering can be roughly divided into: state + computation logic + rendering logic. In React, it's recommended to use hooks to manage state and computation logic together—that is, functions like useState—thereby achieving separation from the HTML and CSS logic of UI rendering.
Sometimes, we need more complex state sharing scenarios. For example, after logging in, different components all need to change because of the login state change. Since useState is local state that lives and dies with component mounting/unmounting, we need to lift state up:
- Lift to the parent component: this way sibling components can share state
- Lift to the global level: use useContext to achieve broader, more global state sharing
But this introduces performance issues: as long as one state changes, all dependent components need to re-render.
So, we need fine-grained management, writing a bunch of conditional logic in useState and useContext—if a component contains that state, only then update it—creating a significant mental burden.
Zustand
Since global state management is necessary, but fine-grained component update management also needs to be designed, why not just use Zustand?
Zustand is a global + centralized state management tool. React globally shares one giant useState (roughly speaking). In React, it looks like this:
// Assume this is a "user/settings" Store
const useUserStore = create(() => ({
userName: 'Alice',
theme: 'dark',
notifications: [],
// ...everything is here
}));
A giant, global store where state and action logic are all encapsulated inside.
When a small component needs to use one of the states, you can write:
const count = useKitchenStore(state => state.counter); // I only want the "counter" ingredient
This way, even if other states in useKitchenStore change, it won't cause components depending on the counter state to re-render.
Since Zustand is a giant, global store, it might work well in large applications because all state is encapsulated in one object, making it very convenient to observe all states.
Jotai
It's also a global state management tool, but it's more distributed—there's no giant object holding all state, but rather scattered into multiple small objects (still global though).
The smallest unit is the Atom, which encapsulates the smallest piece of state and action logic.
// Independent boxes (Atoms)
const counterAtom = atom(0);
const userNameAtom = atom('Alice');
const themeAtom = atom('dark');
When a UI component needs to use a certain state, it looks like this:
const [count] = useAtom(counterAtom); // I directly grab the "counter" box
Since state is naturally distributed across different Atoms, this model's state management naturally has good component rendering performance.
Summary
From now on, I'll use less useContext and useState, and just go all-in on Jotai or Zustand. Write less boilerplate logic whenever possible, finish earlier, and get off work sooner.