useMemoCompare is a wrapper around useMemo that recalculates the value only on a “meaningful” change of dependencies. It supports three comparison forms:
- a dependencies array with shallow index‑by‑index comparison (
===), - a custom comparator function plus a separate value,
- comparator‑only, when the comparison logic is fully encapsulated inside the comparator.
// 1) Dependencies array
function useMemoCompare<ValueType>(
factory: () => ValueType,
deps: unknown[],
): ValueType;
// 2) Custom comparator + value
function useMemoCompare<ValueType, ComparedValue>(
factory: () => ValueType,
compare: (prev: ComparedValue, next: ComparedValue) => boolean,
comparedValue: ComparedValue,
): ValueType;
// 3) Comparator only
function useMemoCompare<ValueType>(
factory: () => ValueType,
compare: () => boolean,
): ValueType;-
Parameters
factory— a function that returns the memoized value; it must be pure and have no side effects.deps— a dependencies array; compared shallowly by index (===).compare— a comparator function that must returntruewhen values are equal (no change) andfalsewhen they differ (there is a change).comparedValue— the value to be compared by the custom comparator.
-
Returns
- The memoized value of type
ValueType.
- The memoized value of type
import { useMemoCompare } from '@webeach/react-hooks/useMemoCompare';
type Item = { title: string; price: number };
function Products({ items, query, sort }: { items: Item[]; query: string; sort: 'asc' | 'desc' }) {
const filtered = useMemoCompare(
() =>
items
.filter((p) => p.title.includes(query))
.sort((a, b) => (sort === 'asc' ? a.price - b.price : b.price - a.price)),
[items, query, sort],
);
return <List data={filtered} />;
}import { useMemoCompare } from '@webeach/react-hooks/useMemoCompare';
function UserBadge({ user }: { user: { id: string; name: string; role: string } | null }) {
const view = useMemoCompare(
() => buildUserView(user),
(prev, next) => prev?.id === next?.id, // recompute only if the id changed
user,
);
return <Badge data={view} />;
}import { useMemoCompare } from '@webeach/react-hooks/useMemoCompare';
declare const lastAppliedHash: { current: string };
type ThemeConfig = { hash: string };
function ThemeProvider({ config }: { config: ThemeConfig }) {
const theme = useMemoCompare(
() => createTheme(config),
() => config.hash === lastAppliedHash.current,
);
return <ThemeContext.Provider value={theme} />;
}-
Recompute trigger
factoryis called only when a change is detected in dependencies. Otherwise, the previous memoized value is returned without recomputation.
-
Dynamic dependencies array
- In the
depsform, the array can be fully dynamic: its length and order may change between renders. The comparison accounts for both values by index and length; a length change is also considered a change. Unlike regularuseMemo, there is no requirement to keep the array length strictly stable across renders.
- In the
-
Factory purity
factorymust be side‑effect free (no state setters, subscriptions, or I/O). Use effects for side effects.
-
Result stability
- When no change is detected, the same reference to the previous value is returned (useful for
React.memo,useEffect, etc.).
- When no change is detected, the same reference to the previous value is returned (useful for
- Expensive computations/transformations that depend on complex objects.
- Data normalization/indexing, view‑model preparation, cacheable selectors.
- When you want to recompute only on semantic changes according to a custom rule.
- If the computation is cheap — extra memoization may overcomplicate the code.
- If you need a callback, not a value — use
useCallbackCompare. - For side effects — use
useEffectCompare/useLayoutEffectCompare.
-
Inverted comparator logic
- The comparator must return
truefor equality andfalsefor difference. Otherwise, you’ll get unnecessary/missed recomputations.
- The comparator must return
-
Side effects inside
factory- Do not call
setState, subscribe, or touch external effects insidefactory.
- Do not call
-
Stale closures
- If
factoryuses outer values, ensure they are up‑to‑date (via parameters, aref, or correct comparisons), otherwise you’ll get outdated data.
- If
-
Wrong comparison key
- In the comparator form, make sure you use the right criterion of “significance” (e.g.,
idinstead of the whole object reference).
- In the comparator form, make sure you use the right criterion of “significance” (e.g.,