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.
mermaidflowchart 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:
bashnpm install zustand react react-dom
tsximport { 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?