@samitouri / QOS-React-2 / commits / f2c381131f

fix: useSyncExternalStoreExtra (#22500)

* move setting memoizedSnapshot * Revert "move setting memoizedSnapshot" This reverts commit f01320689cebfcbc4f3a53208f879ed4a8d6613d. * add failed tests * Revert "Revert "move setting memoizedSnapshot"" This reverts commit cb43c4fdc6b0dcab3480f27d6fbbb3137dbc47bb.

Daishi Kato committed Oct 4, 2021 at 23:39 UTC f2c381131fb58c107e6153255bc8d1d6340db506
2 files changed +90 -1
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+89
@@ -818,4 +818,93 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
818 'Sibling: 1',
819 ]);
820 });
821 +
822 + describe('selector and isEqual error handling in extra', () => {
823 + let ErrorBoundary;
824 + beforeAll(() => {
825 + spyOnDev(console, 'warn');
826 + ErrorBoundary = class extends React.Component {
827 + state = {error: null};
828 + static getDerivedStateFromError(error) {
829 + return {error};
830 + }
831 + render() {
832 + if (this.state.error) {
833 + return <Text text={this.state.error.message} />;
834 + }
835 + return this.props.children;
836 + }
837 + };
838 + });
839 +
840 + it('selector can throw on update', async () => {
841 + const store = createExternalStore({a: 'a'});
842 + const selector = state => state.a.toUpperCase();
843 +
844 + function App() {
845 + const a = useSyncExternalStoreExtra(
846 + store.subscribe,
847 + store.getState,
848 + null,
849 + selector,
850 + );
851 + return <Text text={a} />;
852 + }
853 +
854 + const container = document.createElement('div');
855 + const root = createRoot(container);
856 + await act(() =>
857 + root.render(
858 + <ErrorBoundary>
859 + <App />
860 + </ErrorBoundary>,
861 + ),
862 + );
863 +
864 + expect(container.textContent).toEqual('A');
865 +
866 + await act(() => {
867 + store.set({});
868 + });
869 + expect(container.textContent).toEqual(
870 + "Cannot read property 'toUpperCase' of undefined",
871 + );
872 + });
873 +
874 + it('isEqual can throw on update', async () => {
875 + const store = createExternalStore({a: 'A'});
876 + const selector = state => state.a;
877 + const isEqual = (left, right) => left.a.trim() === right.a.trim();
878 +
879 + function App() {
880 + const a = useSyncExternalStoreExtra(
881 + store.subscribe,
882 + store.getState,
883 + null,
884 + selector,
885 + isEqual,
886 + );
887 + return <Text text={a} />;
888 + }
889 +
890 + const container = document.createElement('div');
891 + const root = createRoot(container);
892 + await act(() =>
893 + root.render(
894 + <ErrorBoundary>
895 + <App />
896 + </ErrorBoundary>,
897 + ),
898 + );
899 +
900 + expect(container.textContent).toEqual('A');
901 +
902 + await act(() => {
903 + store.set({});
904 + });
905 + expect(container.textContent).toEqual(
906 + "Cannot read property 'trim' of undefined",
907 + );
908 + });
909 + });
910 });
packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
+1 -1
@@ -76,7 +76,6 @@ export function useSyncExternalStoreExtra<Snapshot, Selection>(
76 }
77
78 // The snapshot has changed, so we need to compute a new selection.
79 - memoizedSnapshot = nextSnapshot;
79 const nextSelection = selector(nextSnapshot);
80
81 // If a custom isEqual function is provided, use that to check if the data
@@ -87,6 +86,7 @@ export function useSyncExternalStoreExtra<Snapshot, Selection>(
86 return prevSelection;
87 }
88
89 + memoizedSnapshot = nextSnapshot;
90 memoizedSelection = nextSelection;
91 return nextSelection;
92 };