All posts
ReactZustandFrontendState Management

Zustand Is the State Manager React Should Have Had From the Start

May 22, 20266 min read
Zustand Is the State Manager React Should Have Had From the Start

State management in React has always been more complicated than it needs to be. Redux came with actions, reducers, dispatchers, middleware, and a folder structure that took longer to set up than the feature you were building. Context worked for small things but re-rendered everything. Recoil and Jotai introduced atoms. MobX brought observables. At some point the question stopped being about state and started being about which philosophy you wanted to adopt.

Zustand skips all of that. It is a small library that gives you a store, a way to update it, and a hook to subscribe to it. That is the whole thing.

How It Works

You create a store with create, define your state and the functions that update it, and then call it in your components with a selector.

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  reset: () => set({ count: 0 }),
}))

function Counter() {
  const { count, increment } = useStore()
  return <button onClick={increment}>{count}</button>
}

That is a complete working example. No provider, no boilerplate, no file structure to follow. The component only re-renders when count changes because the selector is precise.

Why Not Just Context

React Context re-renders every component that consumes a context value whenever anything in that context changes. For simple things like a theme or the current user, that is fine. For anything that updates frequently, like form state, search queries, or UI state, it creates performance problems that are annoying to debug and fix.

Zustand uses subscriptions. Components only re-render when the specific slice of state they selected has changed. You get the simplicity of Context without the re-render problem.

Async Actions

One thing Zustand does well that Redux made complicated is async. There is no middleware to configure. You just write an async function in your store.

const useUserStore = create((set) => ({
  user: null,
  loading: false,
  fetchUser: async (id) => {
    set({ loading: true })
    const user = await getUser(id)
    set({ user, loading: false })
  },
}))

That is it. No thunks, no sagas, no middleware chain to configure.

When to Use It

Zustand is a good fit for global UI state, shared data that multiple components need, anything that would otherwise require prop drilling through three or more levels, and replacing Redux in codebases where Redux feels like overkill.

For server state, meaning data that comes from an API, you should still reach for React Query or SWR. Those libraries handle caching, revalidation, and loading states better than a Zustand store that you manage manually. Zustand and React Query together cover almost everything most apps need.

The Ecosystem

Zustand has a middleware system for persistence, devtools integration, and immer support if you prefer mutating state directly. The devtools work with Redux DevTools in the browser, so you can inspect your store the same way you would inspect a Redux store.

It is also tiny. Around 1kb gzipped. That matters less than it used to, but it is a signal that the library does not try to solve every problem, which is the reason it solves its actual problem so well.

All postsOussama Fannah