Selectors decide which part of a store a component reads; the selected result and its equality function decide whether that component renders again.
How the pieces fit
mermaidflowchart LR Store["Zustand store"] --> Selector["selector(state)"] Selector --> Result["selected result"] Result --> Equality["Object.is or equality function"] Equality -->|different| Render["component renders"] Equality -->|equal| Skip["component keeps its render"]
The default comparison is Object.is. Selecting an atomic value such as a number, string, or
action usually gives a stable result. A selector that constructs an object or array creates a new
reference, even when its contents are unchanged, so use a stable selector output or a shallow
comparison for that case.
Select atomic values directly
Create the store with create, then pass a selector to the returned hook.
Each selector subscribes the component to one result, so changing another state property does not
change that result.
tsximport { create } from 'zustand'
type MealStore = {
papaBear: string
count: number
setMeal: (meal: string) => void
}
const useMealStore = create<MealStore>()((set) => ({
papaBear: 'large porridge-pot',
count: 0,
setMeal: (meal) => set({ papaBear: meal }),
}))
export default function App() {
const meal = useMealStore((state) => state.papaBear)
const setMeal = useMealStore((state) => state.setMeal)
return (
<main>
<p>Meal: {meal}</p>
<button type="button" onClick={() => setMeal('a large pizza')}>
Order pizza
</button>
</main>
)
}
The component reads the meal and action separately. Clicking the button changes the displayed meal;
the count state is not part of either selected result.
Select several values with useShallow
Wrap a computed selector with useShallow when it returns
an object or array whose entries are shallow-equal to the previous result.
tsximport { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
type FruitStore = {
apples: number
oranges: number
addApple: () => void
}
const useFruitStore = create<FruitStore>()((set) => ({
apples: 1,
oranges: 2,
addApple: () => set((state) => ({ apples: state.apples + 1 })),
}))
export default function App() {
const fruit = useFruitStore(
useShallow((state) => ({ apples: state.apples, oranges: state.oranges })),
)
const addApple = useFruitStore((state) => state.addApple)
return (
<main>
<p>
Apples: {fruit.apples}; oranges: {fruit.oranges}
</p>
<button type="button" onClick={addApple}>
Add apple
</button>
</main>
)
}
For a direct comparison, shallow takes two values and returns a
boolean. It compares object entries and ordered iterable values using Object.is for their entries;
use it when you need to supply an equality function yourself.
Use a custom equality function
In v5, create does not accept a custom equality function. Use
createWithEqualityFn from zustand/traditional
when a store needs a default equality function. The zustand/traditional entry point requires the
use-sync-external-store package.
tsximport { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
type PositionStore = {
x: number
y: number
move: (x: number, y: number) => void
}
const usePositionStore = createWithEqualityFn<PositionStore>()(
(set) => ({
x: 0,
y: 0,
move: (x, y) => set({ x, y }),
}),
shallow,
)
export default function App() {
const position = usePositionStore((state) => ({ x: state.x, y: state.y }))
const move = usePositionStore((state) => state.move)
return (
<main>
<p>
Position: {position.x}, {position.y}
</p>
<button type="button" onClick={() => move(10, 20)}>
Move
</button>
</main>
)
}
The position object is compared shallowly, so the component re-renders when x or y changes and
does not re-render for an update that leaves both selected values equal. Install the peer package
before using this entry point:
bashnpm install zustand react use-sync-external-store
When the store is vanilla and the equality choice belongs to a particular component, use
useStoreWithEqualityFn with the store API, a
selector, and that component's equality function.
tsximport { createStore } from 'zustand'
import { shallow } from 'zustand/shallow'
import { useStoreWithEqualityFn } from 'zustand/traditional'
type PositionStore = {
x: number
y: number
move: (x: number, y: number) => void
}
const positionStore = createStore<PositionStore>()((set) => ({
x: 0,
y: 0,
move: (x, y) => set({ x, y }),
}))
export default function App() {
const position = useStoreWithEqualityFn(
positionStore,
(state) => ({ x: state.x, y: state.y }),
shallow,
)
const move = useStoreWithEqualityFn(positionStore, (state) => state.move, shallow)
return (
<main>
<p>
Position: {position.x}, {position.y}
</p>
<button type="button" onClick={() => move(10, 20)}>
Move
</button>
</main>
)
}
Install React explicitly when using either React entry point; npm install zustand does not add this
optional peer dependency.
bashnpm install zustand react
Bind a vanilla store when the store itself is separate
For a store created outside React, createStore returns a store API.
Pass that API to useStore and give the hook a selector. This keeps the
same selector-and-equality rule while allowing the store instance to be created and supplied by
another part of the application.
tsximport { createStore } from 'zustand'
import { useStore } from 'zustand'
type CounterStore = {
count: number
increment: () => void
}
const counterStore = createStore<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
export default function App() {
const count = useStore(counterStore, (state) => state.count)
const increment = useStore(counterStore, (state) => state.increment)
return (
<main>
<p>Count: {count}</p>
<button type="button" onClick={increment}>
Increment
</button>
</main>
)
}
The component displays the counter from the separate store and updates when count changes.
Avoid unstable selector outputs
Do not return a fresh object or array from a create selector without handling its equality. In v5,
the default Object.is comparison sees each fresh reference as different and the selector can cause
an infinite update loop. Choose one of these forms instead:
- Select each value separately.
- Use
createWithEqualityFnwith an equality function when you need that store-wide behavior.
Use a stable fallback value when a selector returns a fallback function; creating a new fallback function in the selector has the same unstable-reference problem.
Was this page helpful?