Skip to content

Commit a37b65e

Browse files
committed
Fix non-render-phase updates being dropped
Detects if a queue has been processed by whether the hook was cloned. If we change the implementation to an array instead of a list, we'll need some other mechanism to determine whether the hook was processed.
1 parent 1127adf commit a37b65e

File tree

1 file changed

+26
-31
lines changed

1 file changed

+26
-31
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,10 @@ let currentlyRenderingFiber: Fiber = (null: any);
182182
let currentHook: Hook | null = null;
183183
let workInProgressHook: Hook | null = null;
184184

185-
// Updates scheduled during render will trigger an immediate re-render at the
186-
// end of the current pass. We can't store these updates on the normal queue,
187-
// because if the work is aborted, they should be discarded. Because this is
188-
// a relatively rare case, we also don't want to add an additional field to
189-
// either the hook or queue object types. So we store them in a lazily create
190-
// map of queue -> render-phase updates, which are discarded once the component
191-
// completes without re-rendering.
192-
193-
// Whether an update was scheduled during the currently executing render pass.
185+
// Whether an update was scheduled at any point during the render phase. This
186+
// does not get reset if we do another render pass; only when we're completely
187+
// finished evaluating this component. This is an optimization so we know
188+
// whether we need to clear render phase updates after a throw.
194189
let didScheduleRenderPhaseUpdate: boolean = false;
195190

196191
const RE_RENDER_LIMIT = 25;
@@ -416,11 +411,13 @@ export function renderWithHooks(
416411

417412
let children = Component(props, refOrContext);
418413

419-
if (didScheduleRenderPhaseUpdate) {
420-
// Counter to prevent infinite loops.
414+
// Check if there was a render phase update
415+
if (workInProgress.expirationTime === renderExpirationTime) {
416+
// Keep rendering in a loop for as long as render phase updates continue to
417+
// be scheduled. Use a counter to prevent infinite loops.
421418
let numberOfReRenders: number = 0;
422419
do {
423-
didScheduleRenderPhaseUpdate = false;
420+
workInProgress.expirationTime = NoWork;
424421

425422
invariant(
426423
numberOfReRenders < RE_RENDER_LIMIT,
@@ -451,7 +448,7 @@ export function renderWithHooks(
451448
: HooksDispatcherOnRerender;
452449

453450
children = Component(props, refOrContext);
454-
} while (didScheduleRenderPhaseUpdate);
451+
} while (workInProgress.expirationTime === renderExpirationTime);
455452
}
456453

457454
// We can assume the previous dispatcher is always this one, since we set it
@@ -479,8 +476,7 @@ export function renderWithHooks(
479476
hookTypesUpdateIndexDev = -1;
480477
}
481478

482-
// These were reset above
483-
// didScheduleRenderPhaseUpdate = false;
479+
didScheduleRenderPhaseUpdate = false;
484480

485481
invariant(
486482
!didRenderTooFewHooks,
@@ -509,23 +505,21 @@ export function resetHooksAfterThrow(): void {
509505
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
510506

511507
if (didScheduleRenderPhaseUpdate) {
512-
const current = (currentlyRenderingFiber: any).alternate;
513-
if (current !== null) {
514-
// There were render phase updates. These are only valid for this render
515-
// pass, which we are now aborting. Remove the updates from the queues so
516-
// they do not persist to the next render. We already did a single pass
517-
// through the whole list of hooks, so we know that any pending updates
518-
// must have been dispatched during the render phase. The ones that were
519-
// dispatched before we started rendering were already transferred to the
520-
// current hook's queue.
521-
let hook: Hook | null = current.memoizedState;
522-
while (hook !== null) {
523-
const queue = hook.queue;
524-
if (queue !== null) {
525-
queue.pending = null;
526-
}
527-
hook = hook.next;
508+
// There were render phase updates. These are only valid for this render
509+
// phase, which we are now aborting. Remove the updates from the queues so
510+
// they do not persist to the next render. Do not remove updates from hooks
511+
// that weren't processed.
512+
//
513+
// Only reset the updates from the queue if it has a clone. If it does
514+
// not have a clone, that means it wasn't processed, and the updates were
515+
// scheduled before we entered the render phase.
516+
let hook: Hook | null = currentlyRenderingFiber.memoizedState;
517+
while (hook !== null) {
518+
const queue = hook.queue;
519+
if (queue !== null) {
520+
queue.pending = null;
528521
}
522+
hook = hook.next;
529523
}
530524
}
531525

@@ -1306,6 +1300,7 @@ function dispatchAction<S, A>(
13061300
// and apply the stashed updates on top of the work-in-progress hook.
13071301
didScheduleRenderPhaseUpdate = true;
13081302
update.expirationTime = renderExpirationTime;
1303+
currentlyRenderingFiber.expirationTime = renderExpirationTime;
13091304
} else {
13101305
if (
13111306
fiber.expirationTime === NoWork &&

0 commit comments

Comments
 (0)