Skip to content
D
Documentation

Use custom equality functions

how-to
2 min readUpdated

Use this when the default Object.is comparison does not match the rule your selector needs. In React, createWithEqualityFn sets a default comparison for a bound store, while useStoreWithEqualityFn applies a comparison to a store selection.

Before you start

Install Zustand and the peer dependency required by the zustand/traditional entry point:

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

If you only need shallow comparison for an object or array selector, prefer useShallow. Use a custom equality function when your rule differs from shallow equality.

Set a store-wide default

Pass a generic equality function as the second argument to createWithEqualityFn. The mounted component below displays progress from a store; the default comparator considers numeric values equal while they round to the same integer, so the displayed value changes when progress crosses a half-integer threshold.

tsx
import { createWithEqualityFn } from 'zustand/traditional'

type ProgressStore = {
  progress: number
  advance: () => void
}

const sameRoundedValue = <T,>(previous: T, next: T): boolean => {
  if (typeof previous === 'number' && typeof next === 'number') {
    return Math.round(previous) === Math.round(next)
  }

  return Object.is(previous, next)
}

const useProgressStore = createWithEqualityFn<ProgressStore>()(
  (set) => ({
    progress: 0,
    advance: () => set((state) => ({ progress: state.progress + 0.2 })),
  }),
  sameRoundedValue,
)

export default function App() {
  const progress = useProgressStore((state) => state.progress)
  const advance = useProgressStore((state) => state.advance)

  return (
    <section>
      <p>Progress: {progress.toFixed(1)}</p>
      <button type="button" onClick={advance}>
        Advance by 0.2
      </button>
    </section>
  )
}
Look at the mounted progress readout and its advance button in the store-wide equality example.

The custom function is the store hook's default equality function. Because it also receives selections of other types, it uses Object.is for non-numeric values; the advance action therefore retains its normal reference comparison.

Set equality for one selection

For a store API you already have, pass the store, selector, and equality function to useStoreWithEqualityFn. This example uses the API attached to a bound store, then subscribes to its progress value with a per-selection comparison. The page shows tenths for this selection, while the store's own hook continues to use its default comparator.

tsx
import { createWithEqualityFn, useStoreWithEqualityFn } from 'zustand/traditional'

type ProgressStore = {
  progress: number
  advance: () => void
}

const useProgressStore = createWithEqualityFn<ProgressStore>()((set) => ({
  progress: 0,
  advance: () => set((state) => ({ progress: state.progress + 0.2 })),
}))

export default function App() {
  const progress = useStoreWithEqualityFn(
    useProgressStore,
    (state) => state.progress,
    (previous, next) => Math.floor(previous * 10) === Math.floor(next * 10),
  )
  const advance = useProgressStore((state) => state.advance)

  return (
    <section>
      <p>Progress: {progress.toFixed(1)}</p>
      <button type="button" onClick={advance}>
        Advance by 0.2
      </button>
    </section>
  )
}
Look at the mounted progress readout and its advance button in the per-selection equality example.

Here the component's selected value changes in tenths, so each button click updates the displayed value. The equality function compares the selector results, not the complete store state.

Equality choices

OptionTypeDefaultWhat it does
defaultEqualityFn in createWithEqualityFn<U>(a: U, b: U) => booleanObject.isSupplies the default comparison used by the bound store's selectors.
equalityFn in useStoreWithEqualityFn(a: U, b: U) => booleanNo custom function; the underlying selector comparison uses its defaultDecides whether two selected values are equal for this hook call.

Return true only when the previous and next selected values are interchangeable for the component. A comparator that reports unequal values as equal prevents the component from seeing those updates. Keep the comparison aligned with what the component renders.

Pitfalls

  • createWithEqualityFn and useStoreWithEqualityFn come from zustand/traditional; add use-sync-external-store to the application dependencies.
  • The default comparator applies to each selection made through the bound hook. Use the hook's optional per-selection equality argument when one selector needs a different rule.
  • Equality does not change the store update. It controls whether the selector's consumer observes the new selection and re-renders.

Was this page helpful?

Use custom equality functions — zustand · GPT-6 Luna