13
14
// Intentionally not using named imports because Rollup uses dynamic
15
// dispatch for CommonJS interop named imports.
16
-const {useMemo, useDebugValue} = React;
16
+const {useRef, useEffect, useMemo, useDebugValue} = React;
17
18
// Same as useSyncExternalStore, but supports selector and isEqual arguments.
19
export function useSyncExternalStoreExtra<Snapshot, Selection>(
22
selector: (snapshot: Snapshot) => Selection,
23
isEqual?: (a: Selection, b: Selection) => boolean,
24
): Selection {
25
+ // Use this to track the rendered snapshot.
26
+ const instRef = useRef(null);
27
+ let inst;
28
+ if (instRef.current === null) {
29
+ inst = {
30
+ hasValue: false,
31
+ value: (null: Selection | null),
32
+ };
33
+ instRef.current = inst;
34
+ } else {
35
+ inst = instRef.current;
36
+ }
37
+
38
const getSnapshotWithMemoizedSelector = useMemo(() => {
39
// Track the memoized state using closure variables that are local to this
40
// memoized instance of a getSnapshot function. Intentionally not using a
51
hasMemo = true;
52
memoizedSnapshot = nextSnapshot;
53
const nextSelection = selector(nextSnapshot);
54
+ if (isEqual !== undefined) {
55
+ // Even if the selector has changed, the currently rendered selection
56
+ // may be equal to the new selection. We should attempt to reuse the
57
+ // current value if possible, to preserve downstream memoizations.
58
+ if (inst.hasValue) {
59
+ const currentSelection = inst.value;
60
+ if (isEqual(currentSelection, nextSelection)) {
61
+ memoizedSelection = currentSelection;
62
+ return currentSelection;
63
+ }
64
+ }
65
+ }
66
memoizedSelection = nextSelection;
67
return nextSelection;
68
}
92
return nextSelection;
93
};
94
}, [getSnapshot, selector, isEqual]);
95
+
96
const value = useSyncExternalStore(
97
subscribe,
98
getSnapshotWithMemoizedSelector,
99
);
100
+
101
+ useEffect(() => {
102
+ inst.hasValue = true;
103
+ inst.value = value;
104
+ }, [value]);
105
+
106
useDebugValue(value);
107
return value;
108
}