Luôn dùng selector cụ thể trong Zustand — gọi useStore() không có selector subscribe toàn bộ store và re-render khi bất kỳ state nào thay đổi.
Zustand mặc định dùng strict === equality để so sánh — nếu selector trả về primitive (number, string, boolean) thì không vấn đề gì.
Vấn đề xảy ra khi selector trả về object/array mới mỗi lần: useStore(s => ({ a: s.a, b: s.b })) tạo object mới mỗi render → component luôn re-render.
Giải pháp:
- Tách thành 2 selectors riêng:
const a = useStore(s => s.a); const b = useStore(s => s.b). - Dùng
useShallowhook từ 'zustand/react/shallow' để shallow compare object/array:const { a, b } = useStore(useShallow(s => ({ a: s.a, b: s.b }))). - Dùng
createSelectorspattern để auto-generate per-property selectors
Auto-selector pattern: const createSelectors = (store) => { const s = store; s.use = {}; Object.keys(s.getState()).forEach(k => { s.use[k] = () => s(x => x[k]) }); return s } → dùng useStore.use.count().
Lưu ý khác: gọi useStore() không truyền selector → subscribe toàn bộ store, re-render khi bất kỳ state nào thay đổi.
Always use a specific selector in Zustand — calling useStore() without a selector subscribes to the entire store and re-renders on any state change.
Zustand uses strict === equality by default — if the selector returns a primitive (number, string, boolean) there's no issue.
The problem arises when the selector returns a new object/array on every call: useStore(s => ({ a: s.a, b: s.b })) creates a new object every render → component always re-renders.
Solutions:
- Split into two separate selectors:
const a = useStore(s => s.a); const b = useStore(s => s.b). - Use the
useShallowhook from 'zustand/react/shallow' for shallow comparison:const { a, b } = useStore(useShallow(s => ({ a: s.a, b: s.b }))). - Use the
createSelectorspattern to auto-generate per-property selectors
Auto-selector pattern: const createSelectors = (store) => { const s = store; s.use = {}; Object.keys(s.getState()).forEach(k => { s.use[k] = () => s(x => x[k]) }); return s } → use as useStore.use.count().
Another pitfall: calling useStore() with no selector subscribes to the entire store and re-renders whenever any state changes.