Lesson 17 / 25

Selectors and Avoiding Re-renders

Pick small values; use useShallow for objects.

Reference equality decides

A component re-renders when its selector's result changes. Calling the hook with no selector subscribes to the whole store, so the component re-renders on every change. Select primitives or stable references where possible. A selector that builds a new object or array ((s) => ({ a: s.a, b: s.b })) returns a new reference every time; in Zustand 5 this can trigger an infinite update loop error. Wrap such selectors in useShallow, which compares the result shallowly and returns the previous reference when nothing changed.

Good and bad selectors

useShallow for multi-value picks.

import { useShallow } from 'zustand/react/shallow';

// re-renders on any change to the store
const everything = useCart();

// fine: a primitive
const count = useCart((s) => s.items.length);

// fine: actions are stable references
const add = useCart((s) => s.add);

// new object each time -> wrap with useShallow
const { items, clear } = useCart(useShallow((s) => ({ items: s.items, clear: s.clear })));

// new array each time -> also wrap
const ids = useCart(useShallow((s) => s.items.map((i) => i.id)));

Export custom hooks

Exporting small hooks such as useCartCount = () => useCart((s) => s.items.length) keeps selectors consistent and stops components from subscribing to the whole store by accident.

Quick check: Why wrap (s) => ({ a: s.a, b: s.b }) in useShallow?

  • Selectors cannot return objects at all
  • useShallow makes the store persist
  • The selector returns a new object every call, so it needs shallow comparison
  • It converts the store to Redux
Answer

The selector returns a new object every call, so it needs shallow comparison — Shallow comparison keeps the previous reference when values are equal.