# How to Prevent Unnecessary Re-Renders When Selecting Multiple State Slices in Zustand

> Avoid re-renders in Zustand by combining state selections using useShallow or createWithEqualityFn. Optimize your app's performance and reduce subscriptions.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: performance
- Published: 2026-03-06

---

**Combine multiple state selections into a single selector wrapped with `useShallow` or use `createWithEqualityFn` to replace strict equality checks with shallow comparison, reducing multiple subscriptions to one and eliminating redundant renders.**

When you read separate state properties through multiple `useStore` calls in the same component, Zustand creates independent subscriptions for each slice that can fire separately. According to the pmndrs/zustand source code, the idiomatic solution to prevent unnecessary re-renders when selecting multiple state slices in Zustand is to aggregate selections into one selector and apply shallow equality semantics.

## Why Multiple Hooks Cause Extra Re-Renders

By default, the `useStore` hook in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) subscribes to the store using `React.useSyncExternalStore` and compares selector outputs with strict `Object.is` equality. When you write:

```tsx
const nuts = useBearStore(state => state.nuts)   // subscription #1
const honey = useBearStore(state => state.honey) // subscription #2

```

Each call registers its own subscription. If a single update changes both values, React renders the component twice—once per subscription—because each hook independently detects a change. Even if only one value changes, the component re-renders, but with stale props for the unchanged slice.

## The Solution: Single Selector with Shallow Equality

To reduce the subscription count to one, return an object or array containing all needed slices from a single selector. However, because the default equality check uses `Object.is`, a newly created object `{ nuts, honey }` is always considered different from the previous one, triggering a render on every store update regardless of content.

Zustand provides two mechanisms to solve this: `useShallow` for per-selector optimization, and `createWithEqualityFn` for global default behavior.

### Method 1: Using useShallow (Recommended)

The `useShallow` helper exported from `zustand/react/shallow` applies shallow equality (`===` on each property) to the selector’s result. It memoizes the previous output in a ref and only returns a new reference when a shallow property differs, preventing React from seeing a change when object contents are stable.

Implementation detail: `useShallow` lives in [`src/react/shallow.ts`](https://github.com/pmndrs/zustand/blob/main/src/react/shallow.ts) and internally calls the `shallow` utility from [`src/vanilla/shallow.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla/shallow.ts).

```tsx
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

const useBearStore = create(() => ({
  nuts: 0,
  honey: 0,
  treats: {},
}))

export const BearInfo = () => {
  // One subscription, shallow-checked object
  const { nuts, honey } = useBearStore(
    useShallow(state => ({ nuts: state.nuts, honey: state.honey }))
  )

  return <div>{nuts} nuts, {honey} honey</div>
}

```

You can also return an array:

```tsx
const [nuts, honey] = useBearStore(
  useShallow(state => [state.nuts, state.honey])
)

```

With this pattern, the component re-renders only when `nuts` or `honey` actually change, not when unrelated fields like `treats` update.

### Method 2: Using createWithEqualityFn for Global Defaults

If you prefer not to wrap every selector individually, create a store that uses shallow equality by default. The `createWithEqualityFn` factory in [`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts) builds a store whose `useStoreWithEqualityFn` hook (backed by `useSyncExternalStoreWithSelector`) accepts a default equality function applied to all selectors unless overridden.

```tsx
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/vanilla/shallow'

const useBearStore = createWithEqualityFn(
  set => ({
    nuts: 0,
    honey: 0,
    addNut: () => set(state => ({ nuts: state.nuts + 1 })),
  }),
  shallow // default equality for all selectors
)

export const BearInfo = () => {
  const { nuts, honey } = useBearStore(state => ({
    nuts: state.nuts,
    honey: state.honey,
  })) // No useShallow wrapper needed
  return <div>{nuts} – {honey}</div>
}

```

The implementation in [`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts) wires `createWithEqualityFnImpl` to `useStoreWithEqualityFn`, passing your `equalityFn` (here, `shallow`) into the subscription mechanism.

### Method 3: Custom Equality Per Call

For one-off cases where you need shallow equality on a standard store without changing global defaults, import `useStoreWithEqualityFn` directly from `zustand/traditional` and provide the equality function as the third argument:

```tsx
import { create } from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/vanilla/shallow'

const useBearStore = create(() => ({ nuts: 0, honey: 0 }))

export const Bear = () => {
  const { nuts, honey } = useStoreWithEqualityFn(
    useBearStore,
    state => ({ nuts: state.nuts, honey: state.honey }),
    shallow // override equality only for this call
  )
  return <div>{nuts} nuts, {honey} honey</div>
}

```

## Summary

- **Multiple `useStore` calls create multiple subscriptions**, causing components to re-render once per changed slice even within the same update cycle.
- **`useShallow`** from [`src/react/shallow.ts`](https://github.com/pmndrs/zustand/blob/main/src/react/shallow.ts) wraps a selector to apply shallow equality, preventing new object references from triggering renders when property values are unchanged.
- **`createWithEqualityFn`** from [`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts) allows you to set shallow equality as the default for all selectors on a store, eliminating the need for repetitive wrapper code.
- **Single selectors** combined with shallow comparison reduce the subscription count to one and ensure components only render when selected data actually changes.

## Frequently Asked Questions

### Why does selecting multiple slices cause extra renders?

Each call to `useStore` in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) invokes `React.useSyncExternalStore` to create a subscription tied to that specific selector. When state updates, every subscription checks its selector result independently using strict equality (`Object.is`). If you select `nuts` and `honey` separately, both subscriptions fire, causing React to schedule two renders for the same component. Combining selections into one selector reduces this to a single subscription and a single render check.

### What is the difference between useShallow and createWithEqualityFn?

**`useShallow`** is a React hook that memoizes a single selector's output using shallow equality, ideal for standard stores created with `create()`. **`createWithEqualityFn`** is a store factory that builds a store where every selector uses a custom equality function (like shallow) by default. Use `useShallow` for opt-in optimization on specific selectors; use `createWithEqualityFn` when you want shallow comparison to be the default behavior across your entire store.

### Can I use useShallow with array selectors?

Yes. `useShallow` works with any object or array returned from your selector. It performs shallow comparison on each element or property, so `useShallow(state => [state.nuts, state.honey])` only returns a new array reference when `nuts` or `honey` change, preventing unnecessary re-renders just like object selectors.

### Does shallow equality work for nested objects?

No. The `shallow` utility in [`src/vanilla/shallow.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla/shallow.ts) only checks one level deep using strict equality (`===`) on each property. If you select a nested object like `{ treats: state.treats }` where `treats` is itself an object, shallow equality sees the object reference, not its contents. For deeply nested structures, you need to either flatten the selection, use a custom deep equality function (with performance trade-offs), or structure your store to avoid deep nesting in frequently selected slices.