Why does my Zustand counter render on a theme change?
Calling a Zustand hook without a selector subscribes to the whole store state. Destructuring count afterward does not narrow that subscription.
Select count inside the hook. For several selected values, consider separate selectors or useShallow. Shallow is not deep comparison, and parent renders can still occur. Demo: Zustand 5.0.8.
Understand it. Then fix it.
You subscribed to the whole store.
Calling the hook without a selector subscribes to the whole state. Destructuring afterward does not narrow that subscription. The theme update creates a new state object.
const state = useStore();
const { count } = state;Select the value you actually use.
Select count inside the hook. When only theme changes, count is still the same number, so that store update does not trigger this component's render.
const count = useStore(
state => state.count
);Need several values together?
Use separate selectors, or useShallow for an object of selected values. It reuses the previous result when its top-level values are unchanged.
import { useShallow }
from "zustand/react/shallow";
const pair = useStore(useShallow(
s => ({ count: s.count,
add: s.add })
));This fixes one source of updates.
Shallow comparison is not a deep comparison. Update state immutably. Parent renders can still happen, so measure the actual cause before adding more optimization.
Code blocks are teaching excerpts. Keep the surrounding error handling and application requirements.
Save the code excerpts ↓Read the full transcript
Why does my Zustand counter render when the theme changes? It does not even have an opinion about dark mode. Calling the hook without a selector subscribes to the whole state. Destructuring afterward does not narrow that subscription. The theme update creates a new state object. Select count inside the hook. When only theme changes, count is still the same number, so that store update does not trigger this component's render. Use separate selectors, or useShallow for an object of selected values. It reuses the previous result when its top-level values are unchanged. Shallow comparison is not a deep comparison. Update state immutably. Parent renders can still happen, so measure the actual cause before adding more optimization. I subscribed to everything. Apparently the counter is on the all-staff mailing list.