Articles

Zustand vs Redux: Choosing State Management for React

Zustand is a minimal hook-based store with almost no boilerplate; Redux is a stricter, more structured pattern built for large, predictable state trees.

Takina Takina · · 4 min read
Dark-themed code editor showing JavaScript

Zustand is a small state management library that exposes a store as a plain hook with almost no setup, while Redux is a more structured pattern built around actions, reducers, and a single immutable store tree. Both solve the same underlying problem — sharing state across components without prop drilling — but they land at very different points on the boilerplate-versus-structure spectrum.

What each one actually looks like

A Zustand store is a function that returns state and the functions that update it, created with a single create() call:

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

Any component calls useCounterStore((state) => state.count) to subscribe to just that slice of state, and only re-renders when that slice changes. There’s no provider to wrap the app in, no action types to define, and no reducer switch statement.

Redux separates the same logic into three pieces: an action describing what happened, a reducer that computes the new state from the current state and the action, and a store that holds the state and dispatches actions to the reducer. Modern Redux (via Redux Toolkit) trims a lot of the historical boilerplate, but the shape is still: dispatch an action, let a pure reducer function compute the next state, read the result through a selector.

Comparison

ZustandRedux (with Toolkit)
SetupOne create() callStore, slices, provider
BoilerplateMinimalModerate (much less than classic Redux)
Re-render controlAutomatic via selector-based subscriptionsAutomatic via useSelector
DevtoolsSupported via middlewareFirst-class, mature ecosystem
Middleware ecosystemSmall, growingLarge, well established
Enforced structureLoose — you decide the shapeStricter — actions and reducers are separate concerns
Learning curveLowModerate
Best forSmall to mid-size apps, quick iterationLarge apps, teams that want enforced patterns

Why the structure difference matters

Redux’s separation of actions and reducers is opinionated for a reason: it makes state changes traceable. Every state transition is triggered by a dispatched action with a name, which means Redux DevTools can show you a full history of what happened and let you time-travel through it. In a large application with many contributors, that traceability is valuable — it’s easier to answer “what changed this value and why” when every change flows through the same funnel.

Zustand doesn’t enforce that funnel. You can mutate state directly inside any function attached to the store, which is faster to write but easier to lose track of in a codebase with dozens of contributors touching the same store. Neither approach is “wrong” — Redux trades convenience for structure, Zustand trades structure for convenience.

Where React’s built-in state fits in

Not every case calls for a third-party library at all. If state is scoped to one component or a small subtree, useState or useReducer plus React’s context API is often enough — reaching for Zustand or Redux only makes sense once state needs to be shared across many unrelated parts of the tree, or when the update logic gets complex enough to want a testable, isolated reducer. This is a similar shape to the tradeoff covered in signals as a frontend state model — different mechanism, same underlying question of where state should live and who should re-render when it changes.

If you’re building with the Next.js App Router, remember that both Zustand and Redux stores only work inside client components — server components can’t hold interactive state, so the store creation and any component that reads from it needs a "use client" boundary.

Async logic

Redux handles async work (API calls, side effects) through middleware — historically redux-thunk or redux-saga, more recently RTK Query built into Redux Toolkit. It’s a well-trodden path with strong conventions. Zustand has no built-in async story; you just call an async function inside an action and set() the result when it resolves, which is simpler for small cases but leaves you to build your own conventions for loading and error states as the app grows.

When to choose which

Reach for Zustand when the app is small to mid-size, the team is small, and you want to move fast without setting up a store architecture. Reach for Redux when the app is large, multiple teams touch the same state, and the traceability and mature tooling (time-travel debugging, established middleware patterns) are worth the extra ceremony. Many teams also just start with Zustand and migrate a specific slice to Redux later if that slice’s complexity outgrows it — the two aren’t mutually exclusive across a large codebase.

The takeaway

Zustand and Redux both give React components a way to share state outside the component tree, but they optimize for different things: Zustand for minimal setup and fast iteration, Redux for enforced structure and traceability at scale. Start with the simplest tool that solves the actual problem — often that’s useState or context, sometimes it’s Zustand, and only occasionally does an app’s state complexity justify Redux’s full ceremony.

Takina Takina · · 4 min read

React Compiler: Automatic Memoization Explained

The React Compiler automatically memoizes components and values at build time, cutting most manual useMemo, useCallback, and React.memo calls.

#React #JavaScript #Web Development
Takina Takina · · 4 min read

useMemo vs useCallback: When to Use Each in React

useMemo caches a computed value; useCallback caches a function reference. Both skip work on re-render, but they memoize different things.

#JavaScript #React #Web Development