Diagnosing Selector‑Based Re‑Render Issues in Zustand
When a Zustand selector stops re‑rendering, the culprit is often a selector error, faulty equality, or direct state mutation. Follow this step‑by‑step diagnostic guide to find and fix the problem quickly.
09 Nov 2025, 20:10 UTC

Problem: Selector‑Derived Values Stop Re‑Rendering
When a component that uses useStore with a selector stops updating even though the underlying state changes, the most common culprits are selector misuse, faulty equality logic, or direct state mutation. This guide walks through a systematic check‑list to pinpoint the root cause, offers targeted fixes, and outlines when to seek deeper help.
Recognizable Condition
Symptom: A component renders once, then never again after an action that mutates the store.
Typical Sign: No console warnings, but the UI appears stale and no re‑render occurs.
Diagnostic Table
| Cause | Symptom | Quick Check |
|---|---|---|
| Selector does not reference the changing slice | Component never re‑renders | Verify selector accesses the exact state key |
Custom equality returns true incorrectly | Component skips re‑render even on change | Test equality with a simple function that always returns false |
| State update mutates existing object | Re‑render suppressed by shallow compare | Confirm update returns a new reference |
Component uses useStore without a selector | Stale values or unnecessary re‑renders | Add a selector or use shallow |
Ordered Checks
- Verify Selector Accuracy
Ensure the selector function references the exact state key you want to observe. For example, if you want
count:const count = useStore(state => state.count);Check that the selector is pure – no side effects or external closures.
- Test Equality Function
If you supply a custom equality function, replace it temporarily with one that always returns
false:const count = useStore(state => state.count, () => false);If the component re‑renders, the issue lies in the custom equality logic.
- Inspect State Update Immutability
In your store update, avoid direct mutation. Instead of:
set(state => { state.items.push(newItem); });use:
set(state => ({ ...state, items: [...state.items, newItem] }));Verify that the returned object has a new reference using
console.logor a breakpoint. - Check Component Usage
Using
useStorewithout a selector forces the component to re‑render on any state change:const store = useStore(); // not recommended for large storesIf you need multiple values, combine selectors or use
shallowfrom Zustand.
Concrete Example
Below is a minimal counter that demonstrates a failing selector due to direct mutation.
import create from 'zustand';
import { shallow } from 'zustand/shallow';
// Store
const useStore = create(set => ({
count: 0,
increment: () => set(state => {
// BUG: mutating state directly
state.count += 1;
return state;
})
}));
// Component
function Counter() {
const count = useStore(state => state.count, shallow);
const increment = useStore(state => state.increment);
return (
<div>
<span>Count: {count}</span>
<button onClick={increment}>+
Fix by returning a new state object:
increment: () => set(state => ({
...state,
count: state.count + 1
}))
Verification Checklist
- Open the React DevTools profiler and confirm the component re‑renders on button click.
- In the console, log the previous and current
state.countto ensure a new reference. - Remove the custom equality function and verify that re‑renders still occur.
Limitations & Escalation
- Zustand’s default comparison is shallow; deep changes in nested objects still require a new reference.
- If you’re using middleware like
immer, ensure you’re not mutating state inside actions. - When all checks pass but the problem persists, consider:
- Updating to the latest Zustand release.
- Checking for React batching or concurrent mode that might delay updates.
- Reviewing third‑party libraries that might wrap or interfere with the store.
Conclusion
Selector‑based re‑render failures are almost always a symptom of one of the four causes listed. By following the ordered checks, you can quickly isolate the issue, apply the targeted fix, and confirm the component behaves as expected.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.