From 44a8ee05937c170cebcaf106540aa9ef007f81ac Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Tue, 14 Apr 2026 08:41:41 -0700 Subject: [PATCH] useShallowObject: add useMemoShallowStable --- src/common/util/hooks/useShallowObject.ts | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/common/util/hooks/useShallowObject.ts b/src/common/util/hooks/useShallowObject.ts index cded9259b..8722cff7b 100644 --- a/src/common/util/hooks/useShallowObject.ts +++ b/src/common/util/hooks/useShallowObject.ts @@ -56,6 +56,8 @@ export function shallowEquals(objA: T, objB: T) { } +type StableType = T extends any[] ? T : T extends object ? T : never; + /** * Returns a stable object reference when the value has not 'shallow' changed. * Useful to avoid unnecessary re-renders when the object reference changes but @@ -78,7 +80,37 @@ export function useShallowStable(value: T): StableType { }, [value]) as StableType; } -type StableType = T extends any[] ? T : T extends object ? T : never; + +/** + * Like `React.useMemo`, but additionally preserves the previous reference when + * the newly computed value is shallow-equal to the previous one. + * + * Useful when the memo deps are granular enough to invalidate the factory, but + * the resulting object/array often comes out structurally identical - so + * downstream consumers (memoized components, further memos, effects) should + * not see a new reference. + * + * @example + * const summary = useMemoShallowStable(() => ({ + * total: items.reduce((n, i) => n + i.size, 0), + * count: items.length, + * }), [items]); + */ +export function useMemoShallowStable(factory: () => T, deps: React.DependencyList): StableType { + + // holds the last returned value, to preserve identity when shallow-equal + const ref = React.useRef(undefined); + + return React.useMemo(() => { + const next = factory(); + if (ref.current === undefined || !shallowEquals(ref.current, next)) + ref.current = next; + return ref.current; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + deps + ) as StableType; +} /**