@samitouri / QOS-React / commits / e12a9dfc96

Fix production-only updateSyncExternalStore() crash when doing setState in render (#23150)

* Update ReactFiberHooks.new.js * Add regression test + replace-fork * Prettier Co-authored-by: Dan Abramov <dan.abramov@me.com>

Douglas Armstrong committed Jan 20, 2022 at 07:22 UTC e12a9dfc96be12ea8e5c759986041ee5308e8e06
3 files changed +29 -2
packages/react-reconciler/src/ReactFiberHooks.new.js
+1 -1
@@ -2471,7 +2471,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
2471 useDeferredValue: rerenderDeferredValue,
2472 useTransition: rerenderTransition,
2473 useMutableSource: updateMutableSource,
2474 - useSyncExternalStore: mountSyncExternalStore,
2474 + useSyncExternalStore: updateSyncExternalStore,
2475 useId: updateId,
2476
2477 unstable_isNewReconciler: enableNewReconciler,
packages/react-reconciler/src/ReactFiberHooks.old.js
+1 -1
@@ -2471,7 +2471,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
2471 useDeferredValue: rerenderDeferredValue,
2472 useTransition: rerenderTransition,
2473 useMutableSource: updateMutableSource,
2474 - useSyncExternalStore: mountSyncExternalStore,
2474 + useSyncExternalStore: updateSyncExternalStore,
2475 useId: updateId,
2476
2477 unstable_isNewReconciler: enableNewReconciler,
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+27
@@ -745,6 +745,33 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
745 });
746 });
747
748 + test('regression test for #23150', async () => {
749 + const store = createExternalStore('Initial');
750 +
751 + function App() {
752 + const text = useSyncExternalStore(store.subscribe, store.getState);
753 + const [derivedText, setDerivedText] = useState(text);
754 + useEffect(() => {}, []);
755 + if (derivedText !== text.toUpperCase()) {
756 + setDerivedText(text.toUpperCase());
757 + }
758 + return <Text text={derivedText} />;
759 + }
760 +
761 + const container = document.createElement('div');
762 + const root = createRoot(container);
763 + await act(() => root.render(<App />));
764 +
765 + expect(Scheduler).toHaveYielded(['INITIAL']);
766 + expect(container.textContent).toEqual('INITIAL');
767 +
768 + await act(() => {
769 + store.set('Updated');
770 + });
771 + expect(Scheduler).toHaveYielded(['UPDATED']);
772 + expect(container.textContent).toEqual('UPDATED');
773 + });
774 +
775 // The selector implementation uses the lazy ref initialization pattern
776 // @gate !(enableUseRefAccessWarning && __DEV__)
777 test('compares selection to rendered selection even if selector changes', async () => {