Skip to content

Commit

Permalink
Handle render phase updates explicitly
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Oct 13, 2021
1 parent 8ee4ff8 commit c71d3ab
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 12 deletions.
24 changes: 18 additions & 6 deletions packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -454,22 +454,35 @@ export function scheduleUpdateOnFiber(
eventTime: number, eventTime: number,
): FiberRoot | null { ): FiberRoot | null {
checkForNestedUpdates(); checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);


const root = markUpdateLaneFromFiberToRoot(fiber, lane); const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) { if (root === null) {
return null; return null;
} }


// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);

if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
// This update was dispatched during the render phase. This is a mistake
// if the update originates from user space (with the exception of local
// hook updates, which are handled differently and don't reach this
// function), but there are some internal React features that use this as
// an implementation detail, like selective hydration
// and useOpaqueIdentifier.
warnAboutRenderPhaseUpdatesInDEV(fiber);
} else {
// This is a normal update, scheduled from outside the render phase. For
// example, during an input event.
if (enableUpdaterTracking) { if (enableUpdaterTracking) {
if (isDevToolsPresent) { if (isDevToolsPresent) {
addFiberToLanesMap(root, fiber, lane); addFiberToLanesMap(root, fiber, lane);
} }
} }


// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);

if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
if ( if (
(executionContext & CommitContext) !== NoContext && (executionContext & CommitContext) !== NoContext &&
Expand Down Expand Up @@ -533,7 +546,7 @@ export function scheduleUpdateOnFiber(
resetRenderTimer(); resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode(); flushSyncCallbacksOnlyInLegacyMode();
} }

}
return root; return root;
} }


Expand Down Expand Up @@ -2697,7 +2710,6 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
if (__DEV__) { if (__DEV__) {
if ( if (
ReactCurrentDebugFiberIsRenderingInDEV && ReactCurrentDebugFiberIsRenderingInDEV &&
(executionContext & RenderContext) !== NoContext &&
!getIsUpdatingOpaqueValueInRenderPhaseInDEV() !getIsUpdatingOpaqueValueInRenderPhaseInDEV()
) { ) {
switch (fiber.tag) { switch (fiber.tag) {
Expand Down
24 changes: 18 additions & 6 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -454,22 +454,35 @@ export function scheduleUpdateOnFiber(
eventTime: number, eventTime: number,
): FiberRoot | null { ): FiberRoot | null {
checkForNestedUpdates(); checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);


const root = markUpdateLaneFromFiberToRoot(fiber, lane); const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) { if (root === null) {
return null; return null;
} }


// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);

if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
// This update was dispatched during the render phase. This is a mistake
// if the update originates from user space (with the exception of local
// hook updates, which are handled differently and don't reach this
// function), but there are some internal React features that use this as
// an implementation detail, like selective hydration
// and useOpaqueIdentifier.
warnAboutRenderPhaseUpdatesInDEV(fiber);
} else {
// This is a normal update, scheduled from outside the render phase. For
// example, during an input event.
if (enableUpdaterTracking) { if (enableUpdaterTracking) {
if (isDevToolsPresent) { if (isDevToolsPresent) {
addFiberToLanesMap(root, fiber, lane); addFiberToLanesMap(root, fiber, lane);
} }
} }


// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);

if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
if ( if (
(executionContext & CommitContext) !== NoContext && (executionContext & CommitContext) !== NoContext &&
Expand Down Expand Up @@ -533,7 +546,7 @@ export function scheduleUpdateOnFiber(
resetRenderTimer(); resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode(); flushSyncCallbacksOnlyInLegacyMode();
} }

}
return root; return root;
} }


Expand Down Expand Up @@ -2697,7 +2710,6 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
if (__DEV__) { if (__DEV__) {
if ( if (
ReactCurrentDebugFiberIsRenderingInDEV && ReactCurrentDebugFiberIsRenderingInDEV &&
(executionContext & RenderContext) !== NoContext &&
!getIsUpdatingOpaqueValueInRenderPhaseInDEV() !getIsUpdatingOpaqueValueInRenderPhaseInDEV()
) { ) {
switch (fiber.tag) { switch (fiber.tag) {
Expand Down

0 comments on commit c71d3ab

Please sign in to comment.