Skip to main content
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

api
StoreApi<T>
required
The vanilla store instance created with createStore
selector
(state: T) => U
Optional function that selects a portion of the state. If omitted, returns the entire state.
equalityFn
(a: U, b: U) => boolean
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

state
T | U
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

Build docs developers (and LLMs) love