Skip to content

Commit 4de99b3

Browse files
hachibeeDIacdlite
andauthored
fix getSnapshot warning when a selector returns NaN (#23333)
* fix getSnapshot warning when a selector returns NaN useSyncExternalStoreWithSelector delegate a selector as getSnapshot of useSyncExternalStore. * Fiber's use sync external store has a same issue * Small nits We use Object.is to check whether the snapshot value has been updated, so we should also use it to check whether the value is cached. Co-authored-by: Andrew Clark <[email protected]>
1 parent 40eaa22 commit 4de99b3

File tree

4 files changed

+37
-5
lines changed

4 files changed

+37
-5
lines changed

Diff for: packages/react-reconciler/src/ReactFiberHooks.new.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,8 @@ function mountSyncExternalStore<T>(
12901290
nextSnapshot = getSnapshot();
12911291
if (__DEV__) {
12921292
if (!didWarnUncachedGetSnapshot) {
1293-
if (nextSnapshot !== getSnapshot()) {
1293+
const cachedSnapshot = getSnapshot();
1294+
if (!is(nextSnapshot, cachedSnapshot)) {
12941295
console.error(
12951296
'The result of getSnapshot should be cached to avoid an infinite loop',
12961297
);
@@ -1362,7 +1363,8 @@ function updateSyncExternalStore<T>(
13621363
const nextSnapshot = getSnapshot();
13631364
if (__DEV__) {
13641365
if (!didWarnUncachedGetSnapshot) {
1365-
if (nextSnapshot !== getSnapshot()) {
1366+
const cachedSnapshot = getSnapshot();
1367+
if (!is(nextSnapshot, cachedSnapshot)) {
13661368
console.error(
13671369
'The result of getSnapshot should be cached to avoid an infinite loop',
13681370
);

Diff for: packages/react-reconciler/src/ReactFiberHooks.old.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,8 @@ function mountSyncExternalStore<T>(
12901290
nextSnapshot = getSnapshot();
12911291
if (__DEV__) {
12921292
if (!didWarnUncachedGetSnapshot) {
1293-
if (nextSnapshot !== getSnapshot()) {
1293+
const cachedSnapshot = getSnapshot();
1294+
if (!is(nextSnapshot, cachedSnapshot)) {
12941295
console.error(
12951296
'The result of getSnapshot should be cached to avoid an infinite loop',
12961297
);
@@ -1362,7 +1363,8 @@ function updateSyncExternalStore<T>(
13621363
const nextSnapshot = getSnapshot();
13631364
if (__DEV__) {
13641365
if (!didWarnUncachedGetSnapshot) {
1365-
if (nextSnapshot !== getSnapshot()) {
1366+
const cachedSnapshot = getSnapshot();
1367+
if (!is(nextSnapshot, cachedSnapshot)) {
13661368
console.error(
13671369
'The result of getSnapshot should be cached to avoid an infinite loop',
13681370
);

Diff for: packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js

+27
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,33 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
587587
);
588588
});
589589

590+
test('getSnapshot can return NaN without infinite loop warning', async () => {
591+
const store = createExternalStore('not a number');
592+
593+
function App() {
594+
const value = useSyncExternalStore(store.subscribe, () =>
595+
parseInt(store.getState(), 10),
596+
);
597+
return <Text text={value} />;
598+
}
599+
600+
const container = document.createElement('div');
601+
const root = createRoot(container);
602+
603+
// Initial render that reads a snapshot of NaN. This is OK because we use
604+
// Object.is algorithm to compare values.
605+
await act(() => root.render(<App />));
606+
expect(container.textContent).toEqual('NaN');
607+
608+
// Update to real number
609+
await act(() => store.set(123));
610+
expect(container.textContent).toEqual('123');
611+
612+
// Update back to NaN
613+
await act(() => store.set('not a number'));
614+
expect(container.textContent).toEqual('NaN');
615+
});
616+
590617
describe('extra features implemented in user-space', () => {
591618
// The selector implementation uses the lazy ref initialization pattern
592619
// @gate !(enableUseRefAccessWarning && __DEV__)

Diff for: packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export function useSyncExternalStore<T>(
5757
const value = getSnapshot();
5858
if (__DEV__) {
5959
if (!didWarnUncachedGetSnapshot) {
60-
if (value !== getSnapshot()) {
60+
const cachedValue = getSnapshot();
61+
if (!is(value, cachedValue)) {
6162
console.error(
6263
'The result of getSnapshot should be cached to avoid an infinite loop',
6364
);

0 commit comments

Comments
 (0)