useStoreWithEqualityFn is a React Hook that lets you use a vanilla store in React with a custom equality function to control when components re-render.
This hook requires the use-sync-external-store package. Install it with:npm install use-sync-external-store
Import
import { useStoreWithEqualityFn } from 'zustand/traditional'
Signature
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>>(
api: S
): ExtractState<S>
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>, U>(
api: S,
selector: (state: ExtractState<S>) => U,
equalityFn?: (a: U, b: U) => boolean
): U
Parameters
The vanilla store instance created with createStore
Optional function that selects a portion of the state. If omitted, returns the entire state.
Optional function to compare previous and current selected values. If it returns true, the component will not re-render. Common options include shallow from zustand/shallow or Object.is (default).
Returns
Returns the selected state. The component will re-render only when equalityFn returns false.
Usage
Using with shallow equality
import { createStore } from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
type PositionStore = {
position: { x: number; y: number }
setPosition: (pos: { x: number; y: number }) => void
}
const positionStore = createStore<PositionStore>()((set) => ({
position: { x: 0, y: 0 },
setPosition: (position) => set({ position }),
}))
function MovingDot() {
const position = useStoreWithEqualityFn(
positionStore,
(state) => state.position,
shallow
)
const setPosition = useStoreWithEqualityFn(
positionStore,
(state) => state.setPosition,
shallow
)
return (
<div
onPointerMove={(e) => {
setPosition({ x: e.clientX, y: e.clientY })
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
width: 20,
height: 20,
}}
/>
</div>
)
}
Custom equality function
function customEqual(a, b) {
// Only re-render if difference is greater than 10
return Math.abs(a.x - b.x) < 10 && Math.abs(a.y - b.y) < 10
}
function Component() {
const position = useStoreWithEqualityFn(
positionStore,
(state) => state.position,
customEqual
)
// Component only re-renders when position changes by 10 or more pixels
}
Selecting multiple values
function Counter() {
const { count, increment } = useStoreWithEqualityFn(
counterStore,
(state) => ({ count: state.count, increment: state.increment }),
shallow // Prevents re-render if the object shape is the same
)
return <button onClick={increment}>Count: {count}</button>
}
Notes
- Use
shallow from zustand/shallow for shallow object/array comparisons
- The equality function receives
(previousValue, currentValue) and should return true to skip re-render
- More granular control than
useStore for optimizing performance
- Requires
use-sync-external-store package dependency