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 () => {