# How to Use Zustand subscribeWithSelector for Granular State Subscriptions

> Master Zustand subscribeWithSelector for granular state subscriptions. Listen to specific slices with custom equality functions for optimized performance. Optimize your React apps today.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: how-to-guide
- Published: 2026-03-06

---

**`subscribeWithSelector` is a store-mutator middleware in Zustand that extends the base `subscribe` API to let you listen to specific state slices, custom equality functions, and immediate invocation options.**

The `subscribeWithSelector` middleware in the `pmndrs/zustand` repository solves the performance problem of unnecessary re-renders and callback executions by allowing components and vanilla listeners to subscribe to derived values rather than the entire state object.

## What Is Zustand subscribeWithSelector?

`subscribeWithSelector` is a **store-mutator middleware** that intercepts the store creation process and replaces the default `subscribe` method with an overloaded version capable of handling selector functions. According to the source code in [`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts), the middleware registers itself using the mutator identifier `['zustand/subscribeWithSelector', never]` (lines 10-13), which allows TypeScript to automatically augment the store's type definitions.

The middleware wraps the original `subscribe` implementation from [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) (lines 13-14) and provides three distinct overloads for different subscription patterns.

## How subscribeWithSelector Works Under the Hood

The core logic resides in [`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts) (lines 50-73). When you provide a selector function, the middleware:

1. Captures the original `api.subscribe` as `origSubscribe` (line 50)
2. Computes the initial slice value (`currentSlice`) by calling your selector on the current state
3. Creates a wrapper listener that recomputes the slice on every state change, compares it using `equalityFn` (defaulting to `Object.is`), and invokes your callback only when the value genuinely changes (lines 55-62)
4. Supports `fireImmediately: true` to trigger the listener immediately with the current slice value (lines 63-65)

This architecture ensures that listeners only execute when their specific slice of interest changes, eliminating the need for manual comparison logic in consumer code.

## Using subscribeWithSelector in Vanilla JavaScript

For non-React applications, you can create a store with granular subscriptions using the vanilla build:

```typescript
import { createStore } from 'zustand/vanilla';
import { subscribeWithSelector } from 'zustand/middleware';

type PositionStore = {
  position: { x: number; y: number };
  setPosition: (pos: { x: number; y: number }) => void;
};

const positionStore = createStore<PositionStore>()(
  subscribeWithSelector((set) => ({
    position: { x: 0, y: 0 },
    setPosition: (pos) => set({ position: pos }),
  }))
);

// Subscribe to just the x-coordinate
const unsubscribe = positionStore.subscribe(
  (state) => state.position.x,
  (x, previousX) => {
    console.log(`x changed from ${previousX} to ${x}`);
  }
);

// Update both coordinates - listener only fires if x actually changes
positionStore.getState().setPosition({ x: 5, y: 10 });

```

The selector `(state) => state.position.x` ensures the callback only executes when the `x` value changes, even if `y` updates frequently.

## Using subscribeWithSelector with React

In React applications, the `useStore` hook in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) (lines 26-34) automatically leverages the overloaded `subscribe` method when you pass a selector:

```tsx
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import shallow from 'zustand/shallow';

type Todo = { id: number; text: string; completed: boolean };
type TodoStore = {
  todos: Todo[];
  toggle: (id: number) => void;
};

export const useTodoStore = create(
  subscribeWithSelector<TodoStore>((set, get) => ({
    todos: [
      { id: 1, text: 'Learn Zustand', completed: false },
      { id: 2, text: 'Write docs', completed: false },
    ],
    toggle: (id) =>
      set((state) => ({
        todos: state.todos.map((t) =>
          t.id === id ? { ...t, completed: !t.completed } : t
        ),
      })),
  }))
);

function TodoList() {
  // Only re-render when the array reference changes (shallow comparison)
  const todos = useTodoStore(
    (s) => s.todos,
    shallow
  );

  const toggle = useTodoStore((s) => s.toggle);
  
  return (
    <ul>
      {todos.map((t) => (
        <li 
          key={t.id} 
          onClick={() => toggle(t.id)} 
          style={{ textDecoration: t.completed ? 'line-through' : 'none' }}
        >
          {t.text}
        </li>
      ))}
    </ul>
  );
}

```

When `useTodoStore` receives a selector, the underlying `subscribeWithSelector` middleware ensures the component only re-renders when the selected slice changes, not when unrelated state updates occur.

## Advanced Options: Custom Equality and Immediate Invocation

The middleware supports two optional parameters in the third argument:

- **`equalityFn`**: A function to determine if the selected value has changed (defaults to `Object.is`)
- **`fireImmediately`**: Boolean to trigger the listener immediately with the current value

```typescript
import { shallow } from 'zustand/shallow';

// Using the subscribe method directly with options
useTodoStore.subscribe(
  (s) => s.todos,
  (todos) => console.log('Todos updated:', todos),
  { 
    equalityFn: shallow, 
    fireImmediately: true 
  }
);

```

This pattern is particularly useful when integrating with external libraries or when you need to synchronize Zustand state with non-React systems.

## Composing subscribeWithSelector with Other Middleware

`subscribeWithSelector` composes seamlessly with other Zustand middlewares like `devtools` and `persist` because each middleware returns a state-creator function that respects the mutator signature:

```typescript
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';

type Counter = {
  count: number;
  inc: () => void;
};

export const useCounter = create(
  devtools(
    persist(
      subscribeWithSelector<Counter>((set) => ({
        count: 0,
        inc: () => set((s) => ({ count: s.count + 1 })),
      })),
      { name: 'counter-storage' }
    ),
    { name: 'counter-devtools' }
  )
);

```

The final store still exposes the overloaded `subscribe` method from `subscribeWithSelector`, allowing you to use granular subscriptions even with persistence and Redux DevTools enabled.

## Summary

- **`subscribeWithSelector`** is a store-mutator middleware in [`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts) that overloads the base `subscribe` API to support selector functions.
- It wraps the original `subscribe` from [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) and implements slice-level change detection using `Object.is` by default, with support for custom `equalityFn`.
- The middleware supports three overloads: vanilla subscription, selector-based subscription, and selector-based with options (`fireImmediately`, `equalityFn`).
- In React, the `useStore` hook in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) automatically utilizes the overloaded `subscribe`, enabling component-level granularity without extra configuration.
- The middleware composes with `devtools`, `persist`, and other middlewares because it respects the state-creator mutator pattern.

## Frequently Asked Questions

### What is the difference between subscribe and subscribeWithSelector in Zustand?

The base `subscribe` method in [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) notifies listeners on **every** state change, requiring manual filtering inside the callback. `subscribeWithSelector`, implemented in [`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts), extends the API to accept a selector function that extracts a specific slice of state, only invoking the listener when that derived value actually changes according to the specified equality function.

### How do I prevent unnecessary re-renders when using Zustand subscribeWithSelector?

Pass a selector function that returns only the specific state slice your component needs, rather than the entire state object. For React components using the `useStore` hook, the `subscribeWithSelector` middleware automatically ensures the component only re-renders when the selected slice changes. For complex objects or arrays, pass a custom `equalityFn` like `shallow` from `zustand/shallow` to perform reference or shallow equality checks instead of the default `Object.is` deep comparison.

### Can I use subscribeWithSelector with the React useStore hook?

Yes. When you wrap your store with `subscribeWithSelector`, the `useStore` hook in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) automatically detects the overloaded `subscribe` method. Simply pass a selector function as the first argument to `useStore`, and the hook will use the granular subscription mechanism internally, calling `React.useSyncExternalStore` with the selector-aware subscription.

### Does subscribeWithSelector work with Zustand persistence middleware?

Yes, `subscribeWithSelector` composes seamlessly with `persist`, `devtools`, and other middlewares. Because `subscribeWithSelector` follows the state-creator mutator pattern and returns a modified state creator, you can nest it within `persist` or `devtools` calls. The final store retains the overloaded `subscribe` method, allowing you to use granular subscriptions even while persisting state to localStorage or inspecting actions in Redux DevTools.