A small, fast and scalable state management solution for React applications using simplified flux principles
Extensible middleware system for adding DevTools integration, persistence, Redux-style actions, and custom behaviors to Zustand stores.
Integration with Redux DevTools Extension for debugging and development.
/**
* DevTools middleware for debugging with Redux DevTools Extension
* @param initializer - State creator function
* @param devtoolsOptions - DevTools configuration options
* @returns Enhanced state creator with DevTools integration
*/
function devtools<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(
initializer: StateCreator<T, [...Mps, ['zustand/devtools', never]], Mcs, U>,
devtoolsOptions?: DevtoolsOptions
): StateCreator<T, Mps, [['zustand/devtools', never], ...Mcs]>;
interface DevtoolsOptions {
/** Name for the store in DevTools */
name?: string;
/** Enable/disable DevTools integration */
enabled?: boolean;
/** Default action type for unnamed setState calls */
anonymousActionType?: string;
/** Store identifier for multi-store tracking */
store?: string;
}
type NamedSet<T> = (
partial: T | Partial<T> | ((state: T) => T | Partial<T>),
replace?: boolean | undefined,
actionType?: string | undefined,
actionName?: string | undefined
) => void;Usage Examples:
import { create } from "zustand";
import { devtools } from "zustand/middleware";
// Basic DevTools integration
const useStore = create()(
devtools(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }), false, "increment"),
}),
{ name: "counter-store" }
)
);
// Multiple stores with DevTools
const useUserStore = create()(
devtools(
(set) => ({
users: [],
addUser: (user) => set((state) => ({ users: [...state.users, user] }), false, "addUser"),
}),
{ name: "user-store", store: "users" }
)
);
// Conditional DevTools (development only)
const useAppStore = create()(
devtools(
(set) => ({
theme: "light",
toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })),
}),
{ enabled: process.env.NODE_ENV === "development" }
)
);State persistence across page reloads and application restarts with configurable storage backends.
/**
* Persistence middleware for storing state in various storage backends
* @param initializer - State creator function
* @param options - Persistence configuration options
* @returns Enhanced state creator with persistence
*/
function persist<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(
initializer: StateCreator<T, [...Mps, ['zustand/persist', unknown]], Mcs>,
options: PersistOptions<T, U>
): StateCreator<T, Mps, [['zustand/persist', U], ...Mcs]>;
interface PersistOptions<S, PersistedState = S, PersistReturn = unknown> {
/** Storage key name */
name: string;
/** Storage implementation (defaults to localStorage) */
storage?: PersistStorage<PersistedState, PersistReturn>;
/** Function to select which parts of state to persist */
partialize?: (state: S) => PersistedState;
/** Version number for migrations */
version?: number;
/** Migration function for version changes */
migrate?: (persistedState: unknown, version: number) => PersistedState | Promise<PersistedState>;
/** Custom merge function for rehydration */
merge?: (persistedState: unknown, currentState: S) => S;
/** Skip automatic hydration on store creation */
skipHydration?: boolean;
/** Callback when rehydration starts */
onRehydrateStorage?: (state: S) => ((state?: S, error?: unknown) => void) | void;
}
/**
* Creates JSON storage adapter for various storage backends
* @param getStorage - Function returning storage implementation
* @param options - JSON storage options
* @returns PersistStorage instance
*/
function createJSONStorage<S, R = unknown>(
getStorage: () => StateStorage<R>,
options?: JsonStorageOptions
): PersistStorage<S, unknown> | undefined;
interface JsonStorageOptions {
/**
* Function to transform values during JSON parsing (deserialization)
* Called for each key-value pair when retrieving state from storage
*/
reviver?: (key: string, value: unknown) => unknown;
/**
* Function to transform values during JSON serialization
* Called for each key-value pair when saving state to storage
*/
replacer?: (key: string, value: unknown) => unknown;
}
interface StateStorage<R = unknown> {
getItem: (name: string) => string | null | Promise<string | null>;
setItem: (name: string, value: string) => R;
removeItem: (name: string) => R;
}Usage Examples:
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
// Basic localStorage persistence
const useCounterStore = create()(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: "counter-storage" }
)
);
// Partial state persistence
const useAppStore = create()(
persist(
(set) => ({
user: null,
preferences: { theme: "light", lang: "en" },
tempData: "not-persisted",
login: (user) => set({ user }),
updatePreferences: (prefs) => set((state) => ({
preferences: { ...state.preferences, ...prefs }
})),
}),
{
name: "app-storage",
partialize: (state) => ({ user: state.user, preferences: state.preferences }),
}
)
);
// Custom storage backend (sessionStorage)
const useSessionStore = create()(
persist(
(set) => ({
sessionData: {},
updateSession: (data) => set({ sessionData: data }),
}),
{
name: "session-storage",
storage: createJSONStorage(() => sessionStorage),
}
)
);
// With version migration
const useVersionedStore = create()(
persist(
(set) => ({
settings: { version: 2 },
updateSettings: (settings) => set({ settings }),
}),
{
name: "versioned-storage",
version: 2,
migrate: (persistedState, version) => {
if (version === 1) {
// Migrate from version 1 to 2
return { settings: { ...persistedState, version: 2 } };
}
return persistedState;
},
}
)
);Granular subscriptions to specific state slices with custom equality functions.
/**
* Enhances store with selector-based subscriptions
* @param initializer - State creator function
* @returns Enhanced state creator with selective subscriptions
*/
function subscribeWithSelector<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [...Mps, ['zustand/subscribeWithSelector', never]], Mcs>
): StateCreator<T, Mps, [['zustand/subscribeWithSelector', never], ...Mcs]>;Usage Examples:
import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
const useStore = create()(
subscribeWithSelector((set) => ({
count: 0,
user: { name: "John", age: 30 },
increment: () => set((state) => ({ count: state.count + 1 })),
updateUser: (updates) => set((state) => ({
user: { ...state.user, ...updates }
})),
}))
);
// Subscribe to specific state slice
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, prevCount) => {
console.log("Count changed:", prevCount, "->", count);
}
);
// Subscribe with custom equality function
const unsubscribeUser = useStore.subscribe(
(state) => state.user,
(user, prevUser) => {
console.log("User changed:", prevUser, "->", user);
},
{
equalityFn: (prev, curr) => prev.name === curr.name && prev.age === curr.age,
fireImmediately: true,
}
);
// Subscribe to derived state
const unsubscribeAdult = useStore.subscribe(
(state) => state.user.age >= 18,
(isAdult) => {
console.log("Adult status:", isAdult);
}
);Redux-style action/reducer pattern for familiar state management.
/**
* Redux-style middleware with action dispatch
* @param reducer - Reducer function handling state transitions
* @param initialState - Initial state value
* @returns State creator with dispatch capability
*/
function redux<T, A>(
reducer: (state: T, action: A) => T,
initialState: T
): StateCreator<T & { dispatch: (action: A) => A }, [], [], T & { dispatch: (action: A) => A }>;Usage Examples:
import { create } from "zustand";
import { redux } from "zustand/middleware";
type CounterState = { count: number };
type CounterAction =
| { type: "INCREMENT" }
| { type: "DECREMENT" }
| { type: "SET"; payload: number };
const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "SET":
return { count: action.payload };
default:
return state;
}
};
const useCounterStore = create()(
redux(counterReducer, { count: 0 })
);
// Usage
function Counter() {
const { count, dispatch } = useCounterStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
<button onClick={() => dispatch({ type: "SET", payload: 0 })}>Reset</button>
</div>
);
}Utility for combining initial state with state creator functions.
/**
* Combines initial state with state creator for cleaner type inference
* @param initialState - Initial state object
* @param create - State creator function for actions
* @returns Combined state creator
*/
function combine<T extends object, U extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
initialState: T,
create: StateCreator<T, Mps, Mcs, U>
): StateCreator<Write<T, U>, Mps, Mcs>;Usage Examples:
import { create } from "zustand";
import { combine } from "zustand/middleware";
// Without combine - verbose typing
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useBearStore1 = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
// With combine - automatic type inference
const useBearStore2 = create()(
combine(
{ bears: 0 }, // initial state
(set) => ({ // actions
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
})
)
);
// Complex example with nested state
const useAppStore = create()(
combine(
{
user: null as User | null,
settings: { theme: "light", notifications: true },
isLoading: false,
},
(set, get) => ({
login: async (credentials: LoginCredentials) => {
set({ isLoading: true });
const user = await api.login(credentials);
set({ user, isLoading: false });
},
logout: () => set({ user: null }),
updateSettings: (newSettings: Partial<Settings>) =>
set((state) => ({
settings: { ...state.settings, ...newSettings },
})),
})
)
);Immutable state updates using Immer's draft mechanism for mutative syntax.
/**
* Immer middleware for mutative state updates
* @param initializer - State creator function with Immer support
* @returns Enhanced state creator with draft-based mutations
*/
function immer<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>
): StateCreator<T, Mps, [['zustand/immer', never], ...Mcs]>;Usage Examples:
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const useStore = create()(
immer((set) => ({
todos: [] as Todo[],
user: { name: "", preferences: { theme: "light" } },
addTodo: (text: string) =>
set((state) => {
state.todos.push({ id: Date.now(), text, completed: false });
}),
toggleTodo: (id: number) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}),
updateUserPreference: (key: string, value: any) =>
set((state) => {
state.user.preferences[key] = value;
}),
clearCompleted: () =>
set((state) => {
state.todos = state.todos.filter((todo) => !todo.completed);
}),
}))
);Multiple middleware can be composed together, with inner middleware executing first.
import { create } from "zustand";
import { devtools, persist, subscribeWithSelector } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
const useStore = create()(
devtools(
subscribeWithSelector(
persist(
immer((set) => ({
count: 0,
history: [] as number[],
increment: () =>
set((state) => {
state.history.push(state.count);
state.count += 1;
}),
})),
{ name: "counter-storage" }
)
),
{ name: "counter-devtools" }
)
);
// Order matters: immer -> persist -> subscribeWithSelector -> devtoolsInstall with Tessl CLI
npx tessl i tessl/npm-zustand