Skip to content
D
Documentation

Selector subscriptions

concept
2 min readUpdated

A selector defines the value a component subscribes to, and Zustand re-renders that component when the selected value changes. Use a selector for the smallest state value the component needs; wrap a computed object or array selector with useShallow when its contents can stay shallowly equal while the selector creates a new reference.

How selection controls rendering

The store can change while a component's selected value stays the same. Zustand compares selector results with Object.is by default, so a primitive selection such as a number or string does not change when an unrelated field changes. A selector that constructs a new object or array produces a different reference, even when its entries are unchanged. useShallow keeps the previous result when the new result is shallowly equal.

mermaid
flowchart LR
  A["Store update"] --> B["Run component selector"]
  B --> C{"Object.is: same result?"}
  C -->|Yes| D["Keep current render"]
  C -->|No| E["Re-render component"]
  B --> F["useShallow selector"]
  F --> G{"Shallowly equal?"}
  G -->|Yes| D
  G -->|No| E

Selecting the whole store subscribes a component to all store updates. Selecting an atomic value narrows that subscription. When you need a computed selection, useShallow handles arrays or objects whose top-level contents are unchanged; it does not make nested values deeply equal.

Keep a computed selection stable

This mounted React example renders the bear names and Papa Bear's meal separately. Clicking the button changes the meal: the meal display updates, while the names selection remains shallowly equal and its component does not need to re-render.

Install Zustand and React, including React DOM for the mounted example:

bash
npm install zustand react react-dom
tsx
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

type Meals = {
  papaBear: string
  mamaBear: string
  littleBear: string
}

type Store = {
  meals: Meals
  changePapaBearMeal: () => void
}

const useMeals = create<Store>((set) => ({
  meals: {
    papaBear: 'large porridge-pot',
    mamaBear: 'middle-size porridge pot',
    littleBear: 'A little, small, wee pot',
  },
  changePapaBearMeal: () =>
    set((state) => ({
      meals: { ...state.meals, papaBear: 'a large pizza' },
    })),
}))

function BearNames() {
  const names = useMeals(useShallow((state) => Object.keys(state.meals)))

  return <p>Bear names: {names.join(', ')}</p>
}

function PapaBearMeal() {
  const meal = useMeals((state) => state.meals.papaBear)

  return <p>Papa Bear's meal: {meal}</p>
}

function App() {
  const changePapaBearMeal = useMeals((state) => state.changePapaBearMeal)

  return (
    <main>
      <BearNames />
      <PapaBearMeal />
      <button onClick={changePapaBearMeal}>Change Papa Bear's meal</button>
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<App />)

Add <div id="root"></div> to the page that loads this entry point. Before the click, the page shows the three names and Papa Bear's porridge-pot meal. After the click, the meal text changes to pizza; the names stay the same. The names selector creates a fresh key array after the store update, but its entries are unchanged, so useShallow returns the previous array reference and prevents an unnecessary render of BearNames. The meal selector returns a different string, so PapaBearMeal updates.

When to use shallow comparison

Use create to create the store hook, then select a value inside each component. Prefer direct selections for individual primitives or stable action references. Reach for useShallow when one component needs several values as an object or array, or a computed result such as Object.keys(state.meals), and the top-level contents may remain equal across store updates.

Avoid an allocating selector without useShallow when stable output matters: in v5 a selector that returns a new reference can trigger unnecessary renders and may cause an infinite loop. See Select state efficiently for that pitfall and other selector patterns. For immutable updates, see Update nested state; mutating a prior state object does not notify subscribers as an immutable update does.

For a comparison outside a component subscription, shallow returns whether two values are shallowly equal; it is not the component hook's equality-argument API. In v5, create does not accept a custom equality function. Use useShallow for the shallow-equality case rather than passing a second comparator to the store hook.

See it running

The Zustand live demo shows a store-backed interface in the browser. For the authors' focused selector example, see Prevent rerenders with useShallow.

Was this page helpful?

Selector subscriptions — zustand · GPT-6 Luna