Prevent Extra React Re-renders with Zustand shallow Selectors
Use Zustand's shallow utility to prevent React components from re-rendering when store state gets a new reference but unchanged content.
01 Aug 2025, 15:29 UTC

Same data, new reference, unnecessary render
A Zustand selector lets a component subscribe to only the slice of state it needs. By default, Zustand compares the returned slice with strict reference equality (===). If your store creates a new array or object on every update, the component re-renders even when the visible data has not changed.
This often happens with immutable updates:
set({ items: [...state.items, newItem] })
The spread operator creates a new array reference. A component subscribed to state.items will render again, even if the list looks identical to the user.
Use shallow to compare one level deep
Import shallow from zustand/shallow and pass it as the equality function to your store hook:
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'
const useItemStore = create((set) => ({
items: [{ id: 1, name: 'Alpha' }],
replaceItems: (next) => set({ items: next }),
}))
function ItemList() {
const items = useItemStore((state) => state.items, shallow)
return (
{items.map((item) => (
- {item.name}
))}
)
}
Run this in a React component file in your project. No special permissions are required. Replace useItemStore with your own store name.
If another part of the app calls useItemStore.getState().replaceItems([...currentItems]), the array reference changes but the contents do not. With shallow, ItemList skips the render. Without it, the component re-renders.
Trade-offs and limits
shallowcompares only top-level keys and values. A nested object insideitemsthat gets a new reference still triggers a render.- Equality functions run on every store update. A slow custom comparison can block the main thread more than a simple re-render would.
- On very large arrays or objects, the cost of comparing every key may exceed the cost of rendering. Measure before applying
shalloweverywhere.
Check the result yourself
- Add a render counter to the component and log it to the browser console.
- Trigger an update that creates a new state reference with identical content, such as replacing an array with a spread copy.
- Compare the log with and without the
shallowargument. Withshallow, the render count should stay flat for content-identical updates.
If you need deeper comparison, pass a custom function as the third argument: useItemStore(selector, (a, b) => /* your comparison */). Use it only after profiling shows the extra renders are a real problem.
Closing
Start with shallow for object and array slices. It is the cheapest way to stop reference-driven re-renders in Zustand. Keep an eye on nested data, and reach for custom equality only when measurements justify it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.