Skip to content

Commit a0f7e1d

Browse files
committed
refactor: update useSyncExternalStoreWithSelector fork
1 parent f6e522a commit a0f7e1d

File tree

1 file changed

+49
-42
lines changed

1 file changed

+49
-42
lines changed
Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,94 @@
11
// Simplified fork of the useSyncExternalStore hook from the official package,
22
// with `getServerSnapshot` parameter removed and `instRef` simplified using a symbol.
3-
// https://github.com/facebook/react/blob/8d74e8c73a5cc5e461bb1413a74c6b058c6be134/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js
3+
// https://github.com/facebook/react/blob/58bdc0bb967098f14562cd76af0668f2056459a0/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js
44
// MIT Licensed https://github.com/facebook/react/blob/be8aa76873e231555676483a36534bb48ad1b1a3/LICENSE
55

6-
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
6+
/* eslint @stylistic/semi: ["error", "always"] */
7+
/* eslint-disable react-hooks/immutability */
78

8-
const NIL = Symbol('NIL')
9+
import { useDebugValue, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
10+
11+
const NIL = Symbol('NIL');
912

1013
export function useSyncExternalStoreWithSelector<Snapshot, Selection>(
1114
subscribe: (onStoreChange: () => void) => () => void,
1215
getSnapshot: () => Snapshot,
1316
selector: (snapshot: Snapshot) => Selection,
14-
isEqual: (a: Selection, b: Selection) => boolean = Object.is,
17+
isEqual?: (a: Selection, b: Selection) => boolean,
1518
): Selection {
1619
// Use this to track the rendered snapshot.
17-
const instRef = useRef<Selection | typeof NIL>(NIL)
20+
const instRef = useRef<Selection | typeof NIL>(NIL);
1821

19-
const getSelection = useMemo(() => {
22+
const [getSelection] = useMemo(() => {
2023
// Track the memoized state using closure variables that are local to this
2124
// memoized instance of a getSnapshot function. Intentionally not using a
2225
// useRef hook, because that state would be shared across all concurrent
2326
// copies of the hook/component.
24-
let hasMemo = false
25-
let memoizedSnapshot: Snapshot
26-
let memoizedSelection: Selection
27+
let hasMemo = false;
28+
let memoizedSnapshot: Snapshot;
29+
let memoizedSelection: Selection;
2730
const memoizedSelector = (nextSnapshot: Snapshot) => {
2831
if (!hasMemo) {
2932
// The first time the hook is called, there is no memoized result.
30-
// eslint-disable-next-line react-hooks/immutability
31-
hasMemo = true
32-
memoizedSnapshot = nextSnapshot
33-
const nextSelection = selector(nextSnapshot)
34-
// Even if the selector has changed, the currently rendered selection
35-
// may be equal to the new selection. We should attempt to reuse the
36-
// current value if possible, to preserve downstream memoizations.
37-
if (instRef.current !== NIL) {
38-
const currentSelection = instRef.current
39-
if (isEqual(currentSelection, nextSelection)) {
40-
memoizedSelection = currentSelection
41-
return currentSelection
33+
hasMemo = true;
34+
memoizedSnapshot = nextSnapshot;
35+
const nextSelection = selector(nextSnapshot);
36+
if (isEqual !== undefined) {
37+
// Even if the selector has changed, the currently rendered selection
38+
// may be equal to the new selection. We should attempt to reuse the
39+
// current value if possible, to preserve downstream memoizations.
40+
if (instRef.current !== NIL) {
41+
const currentSelection = instRef.current;
42+
if (isEqual(currentSelection, nextSelection)) {
43+
memoizedSelection = currentSelection;
44+
return currentSelection;
45+
}
4246
}
4347
}
44-
memoizedSelection = nextSelection
45-
return nextSelection
48+
memoizedSelection = nextSelection;
49+
return nextSelection;
4650
}
4751

4852
// We may be able to reuse the previous invocation's result.
49-
const prevSnapshot = memoizedSnapshot
50-
const prevSelection = memoizedSelection
53+
const prevSnapshot = memoizedSnapshot;
54+
const prevSelection = memoizedSelection;
5155

5256
if (Object.is(prevSnapshot, nextSnapshot)) {
5357
// The snapshot is the same as last time. Reuse the previous selection.
54-
return prevSelection
58+
return prevSelection;
5559
}
5660

5761
// The snapshot has changed, so we need to compute a new selection.
58-
const nextSelection = selector(nextSnapshot)
62+
const nextSelection = selector(nextSnapshot);
5963

6064
// If a custom isEqual function is provided, use that to check if the data
6165
// has changed. If it hasn't, return the previous selection. That signals
6266
// to React that the selections are conceptually equal, and we can bail
6367
// out of rendering.
64-
if (isEqual(prevSelection, nextSelection)) {
68+
if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
6569
// The snapshot still has changed, so make sure to update to not keep
6670
// old references alive
67-
memoizedSnapshot = nextSnapshot
68-
return prevSelection
71+
memoizedSnapshot = nextSnapshot;
72+
return prevSelection;
6973
}
7074

71-
memoizedSnapshot = nextSnapshot
72-
memoizedSelection = nextSelection
73-
return nextSelection
74-
}
75-
76-
const getSnapshotWithSelector = () => memoizedSelector(getSnapshot())
77-
return getSnapshotWithSelector
78-
}, [getSnapshot, selector, isEqual])
75+
memoizedSnapshot = nextSnapshot;
76+
memoizedSelection = nextSelection;
77+
return nextSelection;
78+
};
79+
const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
80+
return [getSnapshotWithSelector];
81+
}, [getSnapshot, selector, isEqual]);
7982

80-
const selection = useSyncExternalStore(subscribe, getSelection)
83+
const value = useSyncExternalStore(
84+
subscribe,
85+
getSelection,
86+
);
8187

8288
useEffect(() => {
83-
instRef.current = selection
84-
}, [selection])
89+
instRef.current = value;
90+
}, [value]);
8591

86-
return selection
92+
useDebugValue(value);
93+
return value;
8794
}

0 commit comments

Comments
 (0)