Skip to content

Commit c71d3ab

Browse files
committed
Handle render phase updates explicitly
We fire a warning in development if a component is updated during the render phase (with the exception of local hook updates, which have their own defined behavior). Because it's not a supported React pattern, we don't have that many tests that trigger this path. But it is meant to have reasonable semantics when it does happen, so that if it accidentally ships to production, the app doesn't crash unnecessarily. The behavior is not super well-defined, though. There are also some _internal_ React implementation details that intentionally to rely on this behavior. Most prominently, selective hydration and useOpaqueIdentifier. I need to tweak the behavior of render phase updates slightly as part of a fix for useOpaqueIdentifier. This shouldn't cause a user-facing change in behavior outside of useOpaqueIdentifier, but it does require that we explicitly model render phase updates.
1 parent 8ee4ff8 commit c71d3ab

File tree

2 files changed

+36
-12
lines changed

2 files changed

+36
-12
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 18 additions & 6 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -454,22 +454,35 @@ export function scheduleUpdateOnFiber(
454
eventTime: number,
454
eventTime: number,
455
): FiberRoot | null {
455
): FiberRoot | null {
456
checkForNestedUpdates();
456
checkForNestedUpdates();
457-
warnAboutRenderPhaseUpdatesInDEV(fiber);
458

457

459
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
458
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
460
if (root === null) {
459
if (root === null) {
461
return null;
460
return null;
462
}
461
}
463

462

463+
// Mark that the root has a pending update.
464+
markRootUpdated(root, lane, eventTime);
465+
466+
if (
467+
(executionContext & RenderContext) !== NoLanes &&
468+
root === workInProgressRoot
469+
) {
470+
// This update was dispatched during the render phase. This is a mistake
471+
// if the update originates from user space (with the exception of local
472+
// hook updates, which are handled differently and don't reach this
473+
// function), but there are some internal React features that use this as
474+
// an implementation detail, like selective hydration
475+
// and useOpaqueIdentifier.
476+
warnAboutRenderPhaseUpdatesInDEV(fiber);
477+
} else {
478+
// This is a normal update, scheduled from outside the render phase. For
479+
// example, during an input event.
464
if (enableUpdaterTracking) {
480
if (enableUpdaterTracking) {
465
if (isDevToolsPresent) {
481
if (isDevToolsPresent) {
466
addFiberToLanesMap(root, fiber, lane);
482
addFiberToLanesMap(root, fiber, lane);
467
}
483
}
468
}
484
}
469

485

470-
// Mark that the root has a pending update.
471-
markRootUpdated(root, lane, eventTime);
472-
473
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
486
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
474
if (
487
if (
475
(executionContext & CommitContext) !== NoContext &&
488
(executionContext & CommitContext) !== NoContext &&
@@ -533,7 +546,7 @@ export function scheduleUpdateOnFiber(
533
resetRenderTimer();
546
resetRenderTimer();
534
flushSyncCallbacksOnlyInLegacyMode();
547
flushSyncCallbacksOnlyInLegacyMode();
535
}
548
}
536-
549+
}
537
return root;
550
return root;
538
}
551
}
539

552

@@ -2697,7 +2710,6 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2697
if (__DEV__) {
2710
if (__DEV__) {
2698
if (
2711
if (
2699
ReactCurrentDebugFiberIsRenderingInDEV &&
2712
ReactCurrentDebugFiberIsRenderingInDEV &&
2700-
(executionContext & RenderContext) !== NoContext &&
2701
!getIsUpdatingOpaqueValueInRenderPhaseInDEV()
2713
!getIsUpdatingOpaqueValueInRenderPhaseInDEV()
2702
) {
2714
) {
2703
switch (fiber.tag) {
2715
switch (fiber.tag) {

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 18 additions & 6 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -454,22 +454,35 @@ export function scheduleUpdateOnFiber(
454
eventTime: number,
454
eventTime: number,
455
): FiberRoot | null {
455
): FiberRoot | null {
456
checkForNestedUpdates();
456
checkForNestedUpdates();
457-
warnAboutRenderPhaseUpdatesInDEV(fiber);
458

457

459
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
458
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
460
if (root === null) {
459
if (root === null) {
461
return null;
460
return null;
462
}
461
}
463

462

463+
// Mark that the root has a pending update.
464+
markRootUpdated(root, lane, eventTime);
465+
466+
if (
467+
(executionContext & RenderContext) !== NoLanes &&
468+
root === workInProgressRoot
469+
) {
470+
// This update was dispatched during the render phase. This is a mistake
471+
// if the update originates from user space (with the exception of local
472+
// hook updates, which are handled differently and don't reach this
473+
// function), but there are some internal React features that use this as
474+
// an implementation detail, like selective hydration
475+
// and useOpaqueIdentifier.
476+
warnAboutRenderPhaseUpdatesInDEV(fiber);
477+
} else {
478+
// This is a normal update, scheduled from outside the render phase. For
479+
// example, during an input event.
464
if (enableUpdaterTracking) {
480
if (enableUpdaterTracking) {
465
if (isDevToolsPresent) {
481
if (isDevToolsPresent) {
466
addFiberToLanesMap(root, fiber, lane);
482
addFiberToLanesMap(root, fiber, lane);
467
}
483
}
468
}
484
}
469

485

470-
// Mark that the root has a pending update.
471-
markRootUpdated(root, lane, eventTime);
472-
473
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
486
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
474
if (
487
if (
475
(executionContext & CommitContext) !== NoContext &&
488
(executionContext & CommitContext) !== NoContext &&
@@ -533,7 +546,7 @@ export function scheduleUpdateOnFiber(
533
resetRenderTimer();
546
resetRenderTimer();
534
flushSyncCallbacksOnlyInLegacyMode();
547
flushSyncCallbacksOnlyInLegacyMode();
535
}
548
}
536-
549+
}
537
return root;
550
return root;
538
}
551
}
539

552

@@ -2697,7 +2710,6 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2697
if (__DEV__) {
2710
if (__DEV__) {
2698
if (
2711
if (
2699
ReactCurrentDebugFiberIsRenderingInDEV &&
2712
ReactCurrentDebugFiberIsRenderingInDEV &&
2700-
(executionContext & RenderContext) !== NoContext &&
2701
!getIsUpdatingOpaqueValueInRenderPhaseInDEV()
2713
!getIsUpdatingOpaqueValueInRenderPhaseInDEV()
2702
) {
2714
) {
2703
switch (fiber.tag) {
2715
switch (fiber.tag) {

0 commit comments

Comments
 (0)