diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
index 6536f65b7f2a2..ab8143f24034c 100644
--- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
@@ -818,4 +818,93 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
'Sibling: 1',
]);
});
+
+ describe('selector and isEqual error handling in extra', () => {
+ let ErrorBoundary;
+ beforeAll(() => {
+ spyOnDev(console, 'warn');
+ ErrorBoundary = class extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ if (this.state.error) {
+ return ;
+ }
+ return this.props.children;
+ }
+ };
+ });
+
+ it('selector can throw on update', async () => {
+ const store = createExternalStore({a: 'a'});
+ const selector = state => state.a.toUpperCase();
+
+ function App() {
+ const a = useSyncExternalStoreExtra(
+ store.subscribe,
+ store.getState,
+ null,
+ selector,
+ );
+ return ;
+ }
+
+ const container = document.createElement('div');
+ const root = createRoot(container);
+ await act(() =>
+ root.render(
+
+
+ ,
+ ),
+ );
+
+ expect(container.textContent).toEqual('A');
+
+ await act(() => {
+ store.set({});
+ });
+ expect(container.textContent).toEqual(
+ "Cannot read property 'toUpperCase' of undefined",
+ );
+ });
+
+ it('isEqual can throw on update', async () => {
+ const store = createExternalStore({a: 'A'});
+ const selector = state => state.a;
+ const isEqual = (left, right) => left.a.trim() === right.a.trim();
+
+ function App() {
+ const a = useSyncExternalStoreExtra(
+ store.subscribe,
+ store.getState,
+ null,
+ selector,
+ isEqual,
+ );
+ return ;
+ }
+
+ const container = document.createElement('div');
+ const root = createRoot(container);
+ await act(() =>
+ root.render(
+
+
+ ,
+ ),
+ );
+
+ expect(container.textContent).toEqual('A');
+
+ await act(() => {
+ store.set({});
+ });
+ expect(container.textContent).toEqual(
+ "Cannot read property 'trim' of undefined",
+ );
+ });
+ });
});
diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js b/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
index f4a1885aec5b9..aa4957b534753 100644
--- a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
+++ b/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
@@ -76,7 +76,6 @@ export function useSyncExternalStoreExtra(
}
// The snapshot has changed, so we need to compute a new selection.
- memoizedSnapshot = nextSnapshot;
const nextSelection = selector(nextSnapshot);
// If a custom isEqual function is provided, use that to check if the data
@@ -87,6 +86,7 @@ export function useSyncExternalStoreExtra(
return prevSelection;
}
+ memoizedSnapshot = nextSnapshot;
memoizedSelection = nextSelection;
return nextSelection;
};