CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zustand

A small, fast and scalable state management solution for React applications using simplified flux principles

Overview
Eval results
Files

middleware.mddocs/

Middleware

Extensible middleware system for adding DevTools integration, persistence, Redux-style actions, and custom behaviors to Zustand stores.

Capabilities

DevTools Middleware

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" }
  )
);

Persist Middleware

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;
      },
    }
  )
);

Subscribe with Selector Middleware

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 Middleware

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>
  );
}

Combine Middleware

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 },
        })),
    })
  )
);

Immer Middleware

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);
      }),
  }))
);

Middleware Composition

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 -> devtools

Install with Tessl CLI

npx tessl i tessl/npm-zustand

docs

index.md

middleware.md

react-integration.md

store-creation.md

utilities.md

tile.json