Repository: facebook/react
Pull request: Revert "Remove renderPhaseUpdates Map"

Reverts facebook/react#17484

Seems like there's a bug (https://fburl.com/testinfra/hvcgkcp0). Let's revert to unblock the sync.

Changed files:
filename: packages/react-reconciler/src/ReactFiberBeginWork.js
patch: @@ -135,7 +135,7 @@ import {
   calculateChangedBits,
   scheduleWorkOnParentPath,
 } from './ReactFiberNewContext';
-import {renderWithHooks, bailoutHooks} from './ReactFiberHooks';
+import {resetHooks, renderWithHooks, bailoutHooks} from './ReactFiberHooks';
 import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
 import {
   getMaskedContext,
@@ -1318,8 +1318,7 @@ function mountIndeterminateComponent(
     workInProgress.tag = ClassComponent;
 
     // Throw out any hooks that were used.
-    workInProgress.memoizedState = null;
-    workInProgress.updateQueue = null;
+    resetHooks();
 
     // Push context providers early to prevent context stack mismatches.
     // During mounting we don't know the child context yet as the instance doesn't exist.

filename: packages/react-reconciler/src/ReactFiberHooks.js
patch: @@ -192,7 +192,13 @@ let workInProgressHook: Hook | null = null;
 
 // Whether an update was scheduled during the currently executing render pass.
 let didScheduleRenderPhaseUpdate: boolean = false;
-
+// Lazily created map of render-phase updates
+let renderPhaseUpdates: Map<
+  UpdateQueue<any, any>,
+  Update<any, any>,
+> | null = null;
+// Counter to prevent infinite loops.
+let numberOfReRenders: number = 0;
 const RE_RENDER_LIMIT = 25;
 
 // In DEV, this is the name of the currently executing primitive hook
@@ -386,6 +392,8 @@ export function renderWithHooks(
   // workInProgressHook = null;
 
   // didScheduleRenderPhaseUpdate = false;
+  // renderPhaseUpdates = null;
+  // numberOfReRenders = 0;
 
   // TODO Warn if no hooks are used at all during mount, then some are used during update.
   // Currently we will identify the update render as a mount because memoizedState === null.
@@ -417,17 +425,8 @@ export function renderWithHooks(
   let children = Component(props, refOrContext);
 
   if (didScheduleRenderPhaseUpdate) {
-    // Counter to prevent infinite loops.
-    let numberOfReRenders: number = 0;
     do {
       didScheduleRenderPhaseUpdate = false;
-
-      invariant(
-        numberOfReRenders < RE_RENDER_LIMIT,
-        'Too many re-renders. React limits the number of renders to prevent ' +
-          'an infinite loop.',
-      );
-
       numberOfReRenders += 1;
       if (__DEV__) {
         // Even when hot reloading, allow dependencies to stabilize
@@ -447,11 +446,14 @@ export function renderWithHooks(
       }
 
       ReactCurrentDispatcher.current = __DEV__
-        ? HooksDispatcherOnRerenderInDEV
-        : HooksDispatcherOnRerender;
+        ? HooksDispatcherOnUpdateInDEV
+        : HooksDispatcherOnUpdate;
 
       children = Component(props, refOrContext);
     } while (didScheduleRenderPhaseUpdate);
+
+    renderPhaseUpdates = null;
+    numberOfReRenders = 0;
   }
 
   // We can assume the previous dispatcher is always this one, since we set it
@@ -481,6 +483,8 @@ export function renderWithHooks(
 
   // These were reset above
   // didScheduleRenderPhaseUpdate = false;
+  // renderPhaseUpdates = null;
+  // numberOfReRenders = 0;
 
   invariant(
     !didRenderTooFewHooks,
@@ -503,31 +507,14 @@ export function bailoutHooks(
   }
 }
 
-export function resetHooksAfterThrow(): void {
+export function resetHooks(): void {
   // We can assume the previous dispatcher is always this one, since we set it
   // at the beginning of the render phase and there's no re-entrancy.
   ReactCurrentDispatcher.current = ContextOnlyDispatcher;
 
-  if (didScheduleRenderPhaseUpdate) {
-    const current = (currentlyRenderingFiber: any).alternate;
-    if (current !== null) {
-      // There were render phase updates. These are only valid for this render
-      // pass, which we are now aborting. Remove the updates from the queues so
-      // they do not persist to the next render. We already did a single pass
-      // through the whole list of hooks, so we know that any pending updates
-      // must have been dispatched during the render phase. The ones that were
-      // dispatched before we started rendering were already transferred to the
-      // current hook's queue.
-      let hook: Hook | null = current.memoizedState;
-      while (hook !== null) {
-        const queue = hook.queue;
-        if (queue !== null) {
-          queue.pending = null;
-        }
-        hook = hook.next;
-      }
-    }
-  }
+  // This is used to reset the state of this module when a component throws.
+  // It's also called inside mountIndeterminateComponent if we determine the
+  // component is a module-style component.
 
   renderExpirationTime = NoWork;
   currentlyRenderingFiber = (null: any);
@@ -543,6 +530,8 @@ export function resetHooksAfterThrow(): void {
   }
 
   didScheduleRenderPhaseUpdate = false;
+  renderPhaseUpdates = null;
+  numberOfReRenders = 0;
 }
 
 function mountWorkInProgressHook(): Hook {
@@ -678,6 +667,49 @@ function updateReducer<S, I, A>(
 
   queue.lastRenderedReducer = reducer;
 
+  if (numberOfReRenders > 0) {
+    // This is a re-render. Apply the new render phase updates to the previous
+    // work-in-progress hook.
+    const dispatch: Dispatch<A> = (queue.dispatch: any);
+    if (renderPhaseUpdates !== null) {
+      // Render phase updates are stored in a map of queue -> linked list
+      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
+      if (firstRenderPhaseUpdate !== undefined) {
+        renderPhaseUpdates.delete(queue);
+        let newState = hook.memoizedState;
+        let update = firstRenderPhaseUpdate;
+        do {
+          // Process this render phase update. We don't have to check the
+          // priority because it will always be the same as the current
+          // render's.
+          const action = update.action;
+          newState = reducer(newState, action);
+          update = update.next;
+        } while (update !== null);
+
+        // Mark that the fiber performed work, but only if the new state is
+        // different from the current state.
+        if (!is(newState, hook.memoizedState)) {
+          markWorkInProgressReceivedUpdate();
+        }
+
+        hook.memoizedState = newState;
+        // Don't persist the state accumulated from the render phase updates to
+        // the base state unless the queue is empty.
+        // TODO: Not sure if this is the desired semantics, but it's what we
+        // do for gDSFP. I can't remember why.
+        if (hook.baseQueue === null) {
+          hook.baseState = newState;
+        }
+
+        queue.lastRenderedState = newState;
+
+        return [newState, dispatch];
+      }
+    }
+    return [hook.memoizedState, dispatch];
+  }
+
   const current: Hook = (currentHook: any);
 
   // The last rebase update that is NOT part of the base state.
@@ -795,60 +827,6 @@ function updateReducer<S, I, A>(
   return [hook.memoizedState, dispatch];
 }
 
-function rerenderReducer<S, I, A>(
-  reducer: (S, A) => S,
-  initialArg: I,
-  init?: I => S,
-): [S, Dispatch<A>] {
-  const hook = updateWorkInProgressHook();
-  const queue = hook.queue;
-  invariant(
-    queue !== null,
-    'Should have a queue. This is likely a bug in React. Please file an issue.',
-  );
-
-  queue.lastRenderedReducer = reducer;
-
-  // This is a re-render. Apply the new render phase updates to the previous
-  // work-in-progress hook.
-  const dispatch: Dispatch<A> = (queue.dispatch: any);
-  const lastRenderPhaseUpdate = queue.pending;
-  let newState = hook.memoizedState;
-  if (lastRenderPhaseUpdate !== null) {
-    // The queue doesn't persist past this render pass.
-    queue.pending = null;
-
-    const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
-    let update = firstRenderPhaseUpdate;
-    do {
-      // Process this render phase update. We don't have to check the
-      // priority because it will always be the same as the current
-      // render's.
-      const action = update.action;
-      newState = reducer(newState, action);
-      update = update.next;
-    } while (update !== firstRenderPhaseUpdate);
-
-    // Mark that the fiber performed work, but only if the new state is
-    // different from the current state.
-    if (!is(newState, hook.memoizedState)) {
-      markWorkInProgressReceivedUpdate();
-    }
-
-    hook.memoizedState = newState;
-    // Don't persist the state accumulated from the render phase updates to
-    // the base state unless the queue is empty.
-    // TODO: Not sure if this is the desired semantics, but it's what we
-    // do for gDSFP. I can't remember why.
-    if (hook.baseQueue === null) {
-      hook.baseState = newState;
-    }
-
-    queue.lastRenderedState = newState;
-  }
-  return [newState, dispatch];
-}
-
 function mountState<S>(
   initialState: (() => S) | S,
 ): [S, Dispatch<BasicStateAction<S>>] {
@@ -879,12 +857,6 @@ function updateState<S>(
   return updateReducer(basicStateReducer, (initialState: any));
 }
 
-function rerenderState<S>(
-  initialState: (() => S) | S,
-): [S, Dispatch<BasicStateAction<S>>] {
-  return rerenderReducer(basicStateReducer, (initialState: any));
-}
-
 function pushEffect(tag, create, destroy, deps) {
   const effect: Effect = {
     tag,
@@ -1254,6 +1226,12 @@ function dispatchAction<S, A>(
   queue: UpdateQueue<S, A>,
   action: A,
 ) {
+  invariant(
+    numberOfReRenders < RE_RENDER_LIMIT,
+    'Too many re-renders. React limits the number of renders to prevent ' +
+      'an infinite loop.',
+  );
+
   if (__DEV__) {
     if (typeof arguments[3] === 'function') {
       console.error(
@@ -1264,38 +1242,6 @@ function dispatchAction<S, A>(
     }
   }
 
-  const currentTime = requestCurrentTimeForUpdate();
-  const suspenseConfig = requestCurrentSuspenseConfig();
-  const expirationTime = computeExpirationForFiber(
-    currentTime,
-    fiber,
-    suspenseConfig,
-  );
-
-  const update: Update<S, A> = {
-    expirationTime,
-    suspenseConfig,
-    action,
-    eagerReducer: null,
-    eagerState: null,
-    next: (null: any),
-  };
-
-  if (__DEV__) {
-    update.priority = getCurrentPriorityLevel();
-  }
-
-  // Append the update to the end of the list.
-  const pending = queue.pending;
-  if (pending === null) {
-    // This is the first update. Create a circular list.
-    update.next = update;
-  } else {
-    update.next = pending.next;
-    pending.next = update;
-  }
-  queue.pending = update;
-
   const alternate = fiber.alternate;
   if (
     fiber === currentlyRenderingFiber ||
@@ -1305,8 +1251,64 @@ function dispatchAction<S, A>(
     // queue -> linked list of updates. After this render pass, we'll restart
     // and apply the stashed updates on top of the work-in-progress hook.
     didScheduleRenderPhaseUpdate = true;
-    update.expirationTime = renderExpirationTime;
+    const update: Update<S, A> = {
+      expirationTime: renderExpirationTime,
+      suspenseConfig: null,
+      action,
+      eagerReducer: null,
+      eagerState: null,
+      next: (null: any),
+    };
+    if (__DEV__) {
+      update.priority = getCurrentPriorityLevel();
+    }
+    if (renderPhaseUpdates === null) {
+      renderPhaseUpdates = new Map();
+    }
+    const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
+    if (firstRenderPhaseUpdate === undefined) {
+      renderPhaseUpdates.set(queue, update);
+    } else {
+      // Append the update to the end of the list.
+      let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
+      while (lastRenderPhaseUpdate.next !== null) {
+        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
+      }
+      lastRenderPhaseUpdate.next = update;
+    }
   } else {
+    const currentTime = requestCurrentTimeForUpdate();
+    const suspenseConfig = requestCurrentSuspenseConfig();
+    const expirationTime = computeExpirationForFiber(
+      currentTime,
+      fiber,
+      suspenseConfig,
+    );
+
+    const update: Update<S, A> = {
+      expirationTime,
+      suspenseConfig,
+      action,
+      eagerReducer: null,
+      eagerState: null,
+      next: (null: any),
+    };
+
+    if (__DEV__) {
+      update.priority = getCurrentPriorityLevel();
+    }
+
+    // Append the update to the end of the list.
+    const pending = queue.pending;
+    if (pending === null) {
+      // This is the first update. Create a circular list.
+      update.next = update;
+    } else {
+      update.next = pending.next;
+      pending.next = update;
+    }
+    queue.pending = update;
+
     if (
       fiber.expirationTime === NoWork &&
       (alternate === null || alternate.expirationTime === NoWork)
@@ -1411,31 +1413,11 @@ const HooksDispatcherOnUpdate: Dispatcher = {
   useTransition: updateTransition,
 };
 
-const HooksDispatcherOnRerender: Dispatcher = {
-  readContext,
-
-  useCallback: updateCallback,
-  useContext: readContext,
-  useEffect: updateEffect,
-  useImperativeHandle: updateImperativeHandle,
-  useLayoutEffect: updateLayoutEffect,
-  useMemo: updateMemo,
-  useReducer: rerenderReducer,
-  useRef: updateRef,
-  useState: rerenderState,
-  useDebugValue: updateDebugValue,
-  useResponder: createResponderListener,
-  useDeferredValue: updateDeferredValue,
-  useTransition: updateTransition,
-};
-
 let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
 let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
 let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
-let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
 let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
 let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
-let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
 
 if (__DEV__) {
   const warnInvalidContextAccess = () => {
@@ -1812,123 +1794,6 @@ if (__DEV__) {
     },
   };
 
-  HooksDispatcherOnRerenderInDEV = {
-    readContext<T>(
-      context: ReactContext<T>,
-      observedBits: void | number | boolean,
-    ): T {
-      return readContext(context, observedBits);
-    },
-
-    useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
-      currentHookNameInDev = 'useCallback';
-      updateHookTypesDev();
-      return updateCallback(callback, deps);
-    },
-    useContext<T>(
-      context: ReactContext<T>,
-      observedBits: void | number | boolean,
-    ): T {
-      currentHookNameInDev = 'useContext';
-      updateHookTypesDev();
-      return readContext(context, observedBits);
-    },
-    useEffect(
-      create: () => (() => void) | void,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useEffect';
-      updateHookTypesDev();
-      return updateEffect(create, deps);
-    },
-    useImperativeHandle<T>(
-      ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
-      create: () => T,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useImperativeHandle';
-      updateHookTypesDev();
-      return updateImperativeHandle(ref, create, deps);
-    },
-    useLayoutEffect(
-      create: () => (() => void) | void,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useLayoutEffect';
-      updateHookTypesDev();
-      return updateLayoutEffect(create, deps);
-    },
-    useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
-      currentHookNameInDev = 'useMemo';
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
-      try {
-        return updateMemo(create, deps);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useReducer<S, I, A>(
-      reducer: (S, A) => S,
-      initialArg: I,
-      init?: I => S,
-    ): [S, Dispatch<A>] {
-      currentHookNameInDev = 'useReducer';
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
-      try {
-        return rerenderReducer(reducer, initialArg, init);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useRef<T>(initialValue: T): {current: T} {
-      currentHookNameInDev = 'useRef';
-      updateHookTypesDev();
-      return updateRef(initialValue);
-    },
-    useState<S>(
-      initialState: (() => S) | S,
-    ): [S, Dispatch<BasicStateAction<S>>] {
-      currentHookNameInDev = 'useState';
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
-      try {
-        return rerenderState(initialState);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
-      currentHookNameInDev = 'useDebugValue';
-      updateHookTypesDev();
-      return updateDebugValue(value, formatterFn);
-    },
-    useResponder<E, C>(
-      responder: ReactEventResponder<E, C>,
-      props,
-    ): ReactEventResponderListener<E, C> {
-      currentHookNameInDev = 'useResponder';
-      updateHookTypesDev();
-      return createResponderListener(responder, props);
-    },
-    useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
-      currentHookNameInDev = 'useDeferredValue';
-      updateHookTypesDev();
-      return updateDeferredValue(value, config);
-    },
-    useTransition(
-      config: SuspenseConfig | void | null,
-    ): [(() => void) => void, boolean] {
-      currentHookNameInDev = 'useTransition';
-      updateHookTypesDev();
-      return updateTransition(config);
-    },
-  };
-
   InvalidNestedHooksDispatcherOnMountInDEV = {
     readContext<T>(
       context: ReactContext<T>,
@@ -2190,135 +2055,4 @@ if (__DEV__) {
       return updateTransition(config);
     },
   };
-
-  InvalidNestedHooksDispatcherOnRerenderInDEV = {
-    readContext<T>(
-      context: ReactContext<T>,
-      observedBits: void | number | boolean,
-    ): T {
-      warnInvalidContextAccess();
-      return readContext(context, observedBits);
-    },
-
-    useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
-      currentHookNameInDev = 'useCallback';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateCallback(callback, deps);
-    },
-    useContext<T>(
-      context: ReactContext<T>,
-      observedBits: void | number | boolean,
-    ): T {
-      currentHookNameInDev = 'useContext';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return readContext(context, observedBits);
-    },
-    useEffect(
-      create: () => (() => void) | void,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useEffect';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateEffect(create, deps);
-    },
-    useImperativeHandle<T>(
-      ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
-      create: () => T,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useImperativeHandle';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateImperativeHandle(ref, create, deps);
-    },
-    useLayoutEffect(
-      create: () => (() => void) | void,
-      deps: Array<mixed> | void | null,
-    ): void {
-      currentHookNameInDev = 'useLayoutEffect';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateLayoutEffect(create, deps);
-    },
-    useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
-      currentHookNameInDev = 'useMemo';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
-      try {
-        return updateMemo(create, deps);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useReducer<S, I, A>(
-      reducer: (S, A) => S,
-      initialArg: I,
-      init?: I => S,
-    ): [S, Dispatch<A>] {
-      currentHookNameInDev = 'useReducer';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
-      try {
-        return rerenderReducer(reducer, initialArg, init);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useRef<T>(initialValue: T): {current: T} {
-      currentHookNameInDev = 'useRef';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateRef(initialValue);
-    },
-    useState<S>(
-      initialState: (() => S) | S,
-    ): [S, Dispatch<BasicStateAction<S>>] {
-      currentHookNameInDev = 'useState';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      const prevDispatcher = ReactCurrentDispatcher.current;
-      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
-      try {
-        return rerenderState(initialState);
-      } finally {
-        ReactCurrentDispatcher.current = prevDispatcher;
-      }
-    },
-    useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
-      currentHookNameInDev = 'useDebugValue';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateDebugValue(value, formatterFn);
-    },
-    useResponder<E, C>(
-      responder: ReactEventResponder<E, C>,
-      props,
-    ): ReactEventResponderListener<E, C> {
-      currentHookNameInDev = 'useResponder';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return createResponderListener(responder, props);
-    },
-    useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
-      currentHookNameInDev = 'useDeferredValue';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateDeferredValue(value, config);
-    },
-    useTransition(
-      config: SuspenseConfig | void | null,
-    ): [(() => void) => void, boolean] {
-      currentHookNameInDev = 'useTransition';
-      warnInvalidHookAccess();
-      updateHookTypesDev();
-      return updateTransition(config);
-    },
-  };
 }

filename: packages/react-reconciler/src/ReactFiberWorkLoop.js
patch: @@ -140,7 +140,7 @@ import {
 } from './ReactFiberCommitWork';
 import {enqueueUpdate} from './ReactUpdateQueue';
 import {resetContextDependencies} from './ReactFiberNewContext';
-import {resetHooksAfterThrow, ContextOnlyDispatcher} from './ReactFiberHooks';
+import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
 import {createCapturedValue} from './ReactCapturedValue';
 
 import {
@@ -1280,7 +1280,7 @@ function handleError(root, thrownValue) {
     try {
       // Reset module-level state that was set during the render phase.
       resetContextDependencies();
-      resetHooksAfterThrow();
+      resetHooks();
       resetCurrentDebugFiberInDEV();
 
       if (workInProgress === null || workInProgress.return === null) {
@@ -2634,7 +2634,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
       // Keep this code in sync with handleError; any changes here must have
       // corresponding changes there.
       resetContextDependencies();
-      resetHooksAfterThrow();
+      resetHooks();
       // Don't reset current debug fiber, since we're about to work on the
       // same fiber again.
 

filename: packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
patch: @@ -542,50 +542,6 @@ describe('ReactHooksWithNoopRenderer', () => {
       ]);
       expect(ReactNoop.getChildren()).toEqual([span(22)]);
     });
-
-    it('discards render phase updates if something suspends', () => {
-      const thenable = {then() {}};
-      function Foo({signal}) {
-        return (
-          <Suspense fallback="Loading...">
-            <Bar signal={signal} />
-          </Suspense>
-        );
-      }
-
-      function Bar({signal: newSignal}) {
-        let [counter, setCounter] = useState(0);
-        let [signal, setSignal] = useState(true);
-
-        // Increment a counter every time the signal changes
-        if (signal !== newSignal) {
-          setCounter(c => c + 1);
-          setSignal(newSignal);
-          if (counter === 0) {
-            // We're suspending during a render that includes render phase
-            // updates. Those updates should not persist to the next render.
-            Scheduler.unstable_yieldValue('Suspend!');
-            throw thenable;
-          }
-        }
-
-        return <Text text={counter} />;
-      }
-
-      const root = ReactNoop.createRoot();
-      root.render(<Foo signal={true} />);
-
-      expect(Scheduler).toFlushAndYield([0]);
-      expect(root).toMatchRenderedOutput(<span prop={0} />);
-
-      root.render(<Foo signal={false} />);
-      expect(Scheduler).toFlushAndYield(['Suspend!']);
-      expect(root).toMatchRenderedOutput(<span prop={0} />);
-
-      // Rendering again should suspend again.
-      root.render(<Foo signal={false} />);
-      expect(Scheduler).toFlushAndYield(['Suspend!']);
-    });
   });
 
   describe('useReducer', () => {