Learning Zustand - Middleware: devtools & redux
Episode 10 of 23

Learning Zustand - Middleware: devtools & redux

This episode covers Redux DevTools integration through the devtools middleware with time-travel and action tracing, store name configuration, and the redux middleware for using Redux-style reducer patterns inside Zustand.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Watching state change in real time is the key to debugging. Episode 10 connects Zustand to Redux DevTools, the browser extension that gives you time-travel and action tracing. We also cover the redux middleware for teams that want the Redux-style reducer mental model without leaving Zustand.

These two middleware bridge Zustand to the debugging ecosystem and the older patterns many teams are already familiar with.

The devtools Middleware

Redux DevTools Integration

Install the Redux DevTools extension in your browser, then wrap the store with devtools:

JSStore with the devtools middleware
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
 
interface CounterState {
  count: number
  increment: () => void
}
 
export const useCounter = create<CounterState>()(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((s) => ({ count: s.count + 1 })),
    }),
    { name: 'CounterStore' },
  ),
)

devtools(initializer, { name: 'CounterStore' }) sends every set as an action to the extension. In the Redux DevTools panel, you see a list of actions labeled with the store name, the state before and after, and the change diff.

Store Name Configuration and Tracing

The name option separates multiple stores in the same panel:

JSThe name option for several stores
devtools(initializer, { name: 'AuthStore' })
devtools(initializer, { name: 'CartStore' })

{ name: 'AuthStore' } gives each store a unique label in devtools. Zustand also sends metadata about the state-creating function, so actions can be traced back to their source in the code — a debugging convenience classic Redux doesn't have without extra setup.

Time-Travel Debugging

With devtools enabled, the jump button in the extension rewinds state to any point in history. Zustand supports this time-travel natively through the devtools middleware — you can replay a series of actions to understand how a bug emerged.

Info

Redux DevTools runs synchronously and stays active in production if the middleware is installed. Consider enabling devtools only in development so production performance isn't affected.

The redux Middleware

Redux-Style Reducer Pattern

For teams comfortable with reducers, Zustand provides the redux middleware:

JSStore with the redux middleware
import { create } from 'zustand'
import { redux } from 'zustand/middleware'
 
type Action = { type: 'inc'; payload?: number } | { type: 'dec' }
 
function reducer(state: CounterState, action: Action) {
  switch (action.type) {
    case 'inc':
      return { count: state.count + (action.payload ?? 1) }
    case 'dec':
      return { count: state.count - 1 }
    default:
      return state
  }
}
 
const initialState: CounterState = { count: 0 }
 
export const useCounter = create(redux(reducer, initialState))

create(redux(reducer, initialState)) accepts a pure reducer and an initial state. Dispatch actions via the dispatch provided by the store:

JSDispatch an action
const dispatch = useCounter((s) => s.dispatch)
dispatch({ type: 'inc', payload: 2 })

dispatch({ type: 'inc', payload: 2 }) follows the classic Redux pattern: a single source of truth for state, pure testable reducers, and typed actions. Notice there's no Provider or separate store configuration.

When to Use redux

The redux middleware answers specific team needs:

  • Teams migrating from Redux who want a smooth transition.
  • Already-written pure reducers that you want to reuse.
  • Developer habits comfortable with switch action types.

If none of those needs apply, keep using plain set/get — it's more concise and idiomatic. redux is a bridge tool, not the default replacement.

Combining devtools and redux

Both can be combined: wrap redux with devtools so the reducer pattern stays debuggable:

JSredux inside devtools
export const useCounter = create<CounterState>()(
  devtools(redux(reducer, initialState), { name: 'CounterRedux' }),
)

devtools(redux(reducer, initialState), ...) gives you a pure reducer plus time-travel at once. The middleware order determines the store's shape — the outer wrapper is processed last during updates.

Closing

Episode 10 connects Zustand to the Redux ecosystem: the devtools middleware for time-travel and action tracing in the browser extension, and the redux middleware for using Redux-style reducers in a single store without a Provider.

Key takeaways:

  • devtools sends every set to Redux DevTools as an action.
  • The name option separates multiple stores in the devtools panel.
  • Time-travel rewinds state to any point in history.
  • The redux middleware accepts a reducer and initial state, providing dispatch.
  • redux is a bridge for teams familiar with the Redux pattern.
  • Combine devtools(redux(...)) for reducers that stay debuggable.

In the next episode we will discuss advanced persistence and rehydration — custom storage with createJSONStorage for sessionStorage and AsyncStorage, serialization, versioning plus migrate for old state, and handling state that fails to rehydrate.