Zustand Slices

Actions, Selectors and Slices in Zustand

A component subscribes to the store through a selector, a function that picks what it needs. It re-renders only when that value changes. BookNest keeps its selectors next to the store:

src/store/cart.ts: selectors (excerpt)TypeScript
export const selectCount = (s: CartState) =>
  Object.values(s.items).reduce((sum, q) => sum + q, 0);
export const selectQuantity = (id: number) => (s: CartState) => s.items[id] ?? 0;

The tab bar's badge reads useCart(selectCount); the book screen reads useCart((s) => s.add), an action that never changes, and useCart(selectQuantity(id)). Both log their renders. Tapping Add to cart twice on book 3, which already had two copies in the cart, printed (from adb logcat, since Metro's log stream had dropped):

Output of 158
'render tab bar, count', 3
'book 3: data, fetch idle,', 'updated 16:15:46'
'render tab bar, count', 4
'book 3: data, fetch idle,', 'updated 16:15:46'

Only the two subscribers whose values changed re-rendered; the catalog stayed quiet, where Navigation's context re-rendered every consumer. A selector that returns a new object each time, such as (s) => ({ items: s.items, remove: s.remove }), would re-render on every change and, in Zustand 5 58,763 , can loop. Wrap it in useShallow from zustand/react/shallow, as the cart tab does, so the fields are compared one by one.

Outside components, useCart.getState().add(3) calls an action and useCart.subscribe() watches the store, which suits notification handlers and tests. A growing store splits into slices, functions such as createCartSlice that each build part of the state and are spread into one create() call.