# Prevent unnecessary rerenders

Use selectors to subscribe to the values your component renders. Select individual values when possible; wrap computed selections with [`useShallow`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-react-shallow#useshallow) when shallow comparison is sufficient. Use the traditional hooks when you need a custom equality check.

Selector-output equality determines whether a store update causes a component to rerender. The default comparison is `Object.is`; `useShallow` returns a selector that reuses its previous output when the next output is shallowly equal.

## 1. Select individual values

Create a bound store with [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create), then select state and actions separately. This browser example renders a counter and a text input. Changing the text updates `TextInput`, but does not cause a store-driven rerender of `Counter`: neither its selected count nor its action changes.

Use each example below as your React project's `src/main.tsx`, with a `<div id="root"></div>` in its HTML entry point.

Install Zustand and its React peer, plus React DOM to mount the examples:

```bash
npm install zustand react react-dom
```

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'

type CounterStore = {
  count: number
  text: string
  inc: () => void
  setText: (text: string) => void
}

const useCounterStore = create<CounterStore>()((set) => ({
  count: 0,
  text: 'hello',
  inc: () => set((state) => ({ count: state.count + 1 })),
  setText: (text) => set({ text }),
}))

function Counter() {
  const count = useCounterStore((state) => state.count)
  const inc = useCounterStore((state) => state.inc)
  return <button onClick={inc}>Count: {count}</button>
}

function TextInput() {
  const text = useCounterStore((state) => state.text)
  const setText = useCounterStore((state) => state.setText)
  return (
    <label>
      Text <input value={text} onChange={(event) => setText(event.target.value)} />
    </label>
  )
}

function App() {
  return <main><Counter /><TextInput /></main>
}

createRoot(document.getElementById('root')!).render(<App />)
```

Clicking the counter increments the displayed count. Each hook returns its selected value, rather than the entire store.

## 2. Stabilize a computed selection

When you need a computed array or object, pass the selector through `useShallow`. It compares the output, not the whole store, and returns the previous output reference when the comparison succeeds.

For the store and selector pattern, see [How Zustand works](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/how-zustand-works#selectors-define-the-subscription). Run this comparison in your React project's development mode: React's `Profiler` logs commits for a whole-store subscription and a shallow-selected names subscription. Click **Order pizza**: papa bear's displayed meal changes, and both lists remain `papaBear, mamaBear, littleBear`. Look in the browser console for an `update` from `WholeStoreNames`, but not from `BearNames`: the shallow-selected array's entries have not changed.

```tsx title="src/main.tsx"
import { Profiler } from 'react'
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

type Meals = {
  papaBear: string
  mamaBear: string
  littleBear: string
}

const useMeals = create<Meals>()(() => ({
  papaBear: 'large porridge-pot',
  mamaBear: 'middle-size porridge pot',
  littleBear: 'A little, small, wee pot',
}))

function BearNames() {
  const names = useMeals(useShallow((state) => Object.keys(state)))
  return <p>{names.join(', ')}</p>
}

function WholeStoreNames() {
  const meals = useMeals()
  return <p>{Object.keys(meals).join(', ')}</p>
}

function PapaMeal() {
  const meal = useMeals((state) => state.papaBear)
  return (
    <section>
      <p>Papa bear's meal: {meal}</p>
      <button onClick={() => useMeals.setState({ papaBear: 'a large pizza' })}>
        Order pizza
      </button>
    </section>
  )
}

function App() {
  return (
    <main>
      <Profiler id="WholeStoreNames" onRender={(id, phase) => console.log(id, phase)}>
        <WholeStoreNames />
      </Profiler>
      <Profiler id="BearNames" onRender={(id, phase) => console.log(id, phase)}>
        <BearNames />
      </Profiler>
      <PapaMeal />
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<App />)
```

![The bear names appear above papa bear's initial meal and the Order pizza button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/0a1fb276329fcab7846effd4e5530f96.png)

The same wrapper works for multiple state picks in an object or array. For example, select both a value and its action inside the wrapped selector. Shallow comparison checks top-level entries with `Object.is`; it does not recursively compare nested objects. A newly created nested object still compares differently.

## 3. Use custom equality when shallow comparison is not enough

In v5, `create` does not support a custom equality function. Use [`createWithEqualityFn`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-traditional#createwithequalityfn) from `zustand/traditional` for a bound hook with custom equality, or keep `create` and use `useShallow` for shallow comparison.

Install the traditional entry point's peer dependency in your project:

```bash
npm install zustand react react-dom use-sync-external-store
```

Pass the shipped [`shallow`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-shallow#shallow) comparator as the store's default equality function. You can override it for an individual subscription by passing a second argument to the bound hook.

This example displays the number of completed groups of ten. Each click increments the store's count, but `GroupsOfTen` keeps its selected output while both counts belong to the same group. Its displayed value changes from zero to one on the tenth click.

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'

type CounterStore = {
  count: number
  inc: () => void
}

const useCounterStore = createWithEqualityFn<CounterStore>()(
  (set) => ({
    count: 0,
    inc: () => set((state) => ({ count: state.count + 1 })),
  }),
  shallow,
)

function sameGroup(previous: number, next: number) {
  return Math.floor(previous / 10) === Math.floor(next / 10)
}

function GroupsOfTen() {
  const count = useCounterStore((state) => state.count, sameGroup)
  return <p>Completed groups of ten: {Math.floor(count / 10)}</p>
}

function IncrementButton() {
  const inc = useCounterStore((state) => state.inc)
  return <button onClick={inc}>Add one</button>
}

function App() {
  return <main><GroupsOfTen /><IncrementButton /></main>
}

createRoot(document.getElementById('root')!).render(<App />)
```

Only ignore differences your component does not render. Here, the component renders the group number, not the raw count.

For an existing vanilla store created with [`createStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#createstore), use [`useStoreWithEqualityFn`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-traditional#usestorewithequalityfn). Pass the store instance, a selector, and an optional equality function. It returns the selected data and applies that equality check to the subscription; you do not need to recreate the store as a bound hook.

Use [`useStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#usestore) for subscriptions that do not need custom equality. This mounted example uses it to select the increment action and uses `useStoreWithEqualityFn` to select the count with the group comparison. Click **Add one** ten times to change the displayed group from zero to one.

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { createStore, useStore } from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'

type CounterStore = {
  count: number
  inc: () => void
}

const counterStore = createStore<CounterStore>()((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

function sameGroup(previous: number, next: number) {
  return Math.floor(previous / 10) === Math.floor(next / 10)
}

function GroupsOfTen() {
  const count = useStoreWithEqualityFn(
    counterStore,
    (state) => state.count,
    sameGroup,
  )
  return <p>Completed groups of ten: {Math.floor(count / 10)}</p>
}

function IncrementButton() {
  const inc = useStore(counterStore, (state) => state.inc)
  return <button onClick={inc}>Add one</button>
}

function App() {
  return <main><GroupsOfTen /><IncrementButton /></main>
}

createRoot(document.getElementById('root')!).render(<App />)
```

## Comparison parameters

These APIs take selector and comparison arguments rather than an options object.

| Argument | Type | Default | What it does |
| --- | --- | --- | --- |
| `useShallow` selector | `(state: S) => U` | Required | Computes the output whose previous reference is reused when shallowly equal. |
| `createWithEqualityFn` default equality | `<U>(a: U, b: U) => boolean` | `undefined` | Supplies the comparison for bound-hook calls that do not pass their own equality function. |
| Bound hook equality | `(a: U, b: U) => boolean` | Store's default equality | Overrides the comparison for that subscription. |
| `useStoreWithEqualityFn` equality | `(a: U, b: U) => boolean` | `undefined` | Supplies the comparison for the selected output from a vanilla store. |

## Pitfalls and verification

- Shallow equality is not deep equality. Select the primitive fields you render when nested object references change but those fields do not.
- These comparisons prevent store-driven rerenders, not renders caused by a parent or local React state. Use React DevTools' Profiler to inspect which components commit when you click the examples' buttons.
- For stable selector outputs in v5, see [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions).

## Live demo and related guides

See a bound store and counter running in the [Zustand live demo](https://zustand-demo.pmnd.rs/).

- [React quick start](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/react-quick-start) — set up your first store and React component.
- [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions) — define what each component subscribes to.
- [State and actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/state-and-actions) — preserve references correctly when updating state.
