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:
bashnpm 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.
tsximport { 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>
)
}
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.
tsximport { 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>
)
}
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
| Option | Type | Default | What it does |
|---|---|---|---|
defaultEqualityFn in createWithEqualityFn | <U>(a: U, b: U) => boolean | Object.is | Supplies the default comparison used by the bound store's selectors. |
equalityFn in useStoreWithEqualityFn | (a: U, b: U) => boolean | No custom function; the underlying selector comparison uses its default | Decides 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
createWithEqualityFnanduseStoreWithEqualityFncome fromzustand/traditional; adduse-sync-external-storeto 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.
Related
- Select state efficiently for selector patterns and shallow comparison.
- Use a vanilla store in React for subscribing to a standalone store in a component.
Was this page helpful?