Persistence plus Redux DevTools
A browser store must survive reloads while remaining inspectable during development.
Zustand · Const
Zustand middleware is composed by nesting state-creator wrappers, such as persist inside devtools, and passing the result directly to create. This lets one store combine persistence, debugging, reducers, Immer updates, or other documented extensions.
Middleware composition wraps one state creator with another, allowing several independent store enhancements to be applied together. Put the composed wrappers immediately inside create so TypeScript can infer the contextual types correctly; the documentation demonstrates nesting persist inside devtools.
create<State>()(middlewareA(middlewareB(stateCreator, options), options))A composed state creator passed to create; the wrapped middleware behaviors operate on the resulting Zustand store.
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
type BearState = {
bears: number
increase: () => void
}
export const useBearStore = create<BearState>()(
devtools(
persist(
(set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
}),
{ name: 'bear-store' },
),
),
)The store creator is wrapped first with persist and then with devtools, composing both behaviors directly inside create.
A browser store must survive reloads while remaining inspectable during development.
A large store is assembled from multiple independently defined slices.
An application uses reducer-style updates alongside Zustand's store API.
State updates should use convenient mutable syntax while retaining immutable behavior.
Middleware is composed by nesting state-creator wrappers, such as devtools(persist(stateCreator, options)). The resulting creator is passed directly to create, so both middleware layers affect the same store.
Follow-up: How should middleware be applied to a sliced store? Apply it once around the combined store creator, not independently inside each slice, because slice-level application can cause unexpected behavior.
The documented TypeScript pattern is create<State>()(middleware(stateCreator)). The extra invocation preserves the explicit state annotation while allowing the middleware wrappers to receive the creator in context.
Follow-up: When might the curried form be unnecessary? Middleware such as combine creates state and can infer it, so the non-curried create(combine(...)) form is suitable.
A combined store can be wrapped with middleware in the same way as an ordinary store. For example, persist can wrap the creator that spreads multiple slice creators, with its storage options supplied alongside the creator.
Follow-up: What is the principal design rule for slices? Keep middleware in the combined root store rather than embedding it in individual slice creators.
persistdevtoolsreduximmercombinecreate