Cutting Unnecessary Re‑Renders in React with Zustand Selectors
Learn how Zustand’s selector feature lets React components subscribe only to the data they need, reducing re‑renders and keeping state logic simple. A worked example shows the performance difference and highlights common pitfalls.
27 Jan 2026, 13:47 UTC

Problem: Too Many Re‑Renders in a Simple Counter App
In a typical React + Zustand setup, every time you call setState the entire store is considered changed. Even if a component only needs a single value, the default subscription pattern forces it to re‑render on every store update. For a small list or a counter, that overhead is trivial, but in a large application with dozens of components, the cost can add up.
Thesis: Use Selectors to Slice the Store
Zustand’s useStore hook accepts an optional selector callback. The component subscribes only to the selector’s output, and React re‑renders only when that output changes. This gives you fine‑grained control over which parts of the store trigger updates.
How Selectors Work
- Store shape stays flat. The store is a plain JavaScript object; selectors read from it but never modify it.
- Selector output is compared. Zustand uses
Object.isto compare the previous and current selector results. If they are identical, the component is not updated. - Memoization is per subscription. Each selector call is memoized for that component instance, so repeated renders with unchanged data are skipped.
Worked Example: Counter With and Without a Selector
Below is a minimal React app that demonstrates the difference. Run it in a sandbox or your local environment to see the console logs.
import create from 'zustand';
import { useEffect } from 'react';
// 1️⃣ Create a simple store
const useStore = create(set => ({
count: 0,
inc: () => set(state => ({ count: state.count + 1 }))
}));
// 2️⃣ Component that subscribes to the whole store
function FullCounter() {
const { count, inc } = useStore(); // subscribes to everything
useEffect(() => console.log('FullCounter rendered'), [count]);
return (
Full Count: {count}
Increment
);
}
// 3️⃣ Component that uses a selector
function SlicedCounter() {
const count = useStore(state => state.count); // subscribes only to count
useEffect(() => console.log('SlicedCounter rendered'), [count]);
return (
Sliced Count: {count}
Increment
);
}
export default function App() {
return (
<div>
<FullCounter />
<SlicedCounter />
</div>
);
}
When you click the button, FullCounter re‑renders every time because it receives the entire store object. SlicedCounter only re‑renders when count changes, even though the store’s shape is unchanged. In a real app, the difference becomes significant when many components subscribe to unrelated parts of the state.
Common Pitfall: Returning New Objects
Selectors that return a new object on each call will defeat the memoization. Consider this selector:
const badSelector = state => ({ value: state.count });
Because { value: state.count } creates a new object every render, Object.is always sees a difference, causing the component to re‑render on every store update. To keep the benefit, return primitives or memoize expensive calculations inside the selector:
const goodSelector = state => {
return state.count;
// or for derived data:
// return useMemo(() => expensiveCalc(state), [state.count]);
};
Trade‑Off: Flat Store vs. Nested State
Zustand encourages a flat store for simplicity. If you need nested state, you can still use selectors to drill down, but be careful not to expose mutable nested objects. Prefer primitives or immutable updates so that selector comparison works correctly.
Actionable Next Steps
- Refactor components to use
useStore(state => state.someValue)instead of the whole store. - Audit selectors for object creation; replace them with primitives or memoized values.
- Use React DevTools Profiler to measure render counts before and after adding selectors.
- Keep the store flat; if you must nest, wrap nested data in immutable structures or shallow copy when updating.
By following these guidelines, you can harness Zustand’s selector feature to keep your React components lean, responsive, and free from unnecessary re‑renders.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.