From 8fc9385906cce5bc196f2284c771631badf49d2c Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 13 Sep 2022 18:03:20 +0100 Subject: [PATCH 01/17] Expose ref to Offscreen if mode is manual --- packages/react-reconciler/src/ReactFiberBeginWork.new.js | 4 +++- packages/react-reconciler/src/ReactFiberBeginWork.old.js | 4 +++- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 8 ++++++++ packages/react-reconciler/src/ReactFiberCommitWork.old.js | 8 ++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 66c679b424646..170d403753a1b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -684,7 +684,9 @@ function updateOffscreenComponent( const prevState: OffscreenState | null = current !== null ? current.memoizedState : null; - markRef(current, workInProgress); + if (nextProps.mode === 'manual') { + markRef(current, workInProgress); + } if ( nextProps.mode === 'hidden' || diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index e5c3f1f4cb562..2267d8f755948 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -684,7 +684,9 @@ function updateOffscreenComponent( const prevState: OffscreenState | null = current !== null ? current.memoizedState : null; - markRef(current, workInProgress); + if (nextProps.mode === 'manual') { + markRef(current, workInProgress); + } if ( nextProps.mode === 'hidden' || diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 343cc152d735c..0a439875062d5 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -1192,6 +1192,14 @@ function commitLayoutEffectOnFiber( offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden; offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; } + + if (finishedWork.pendingProps.mode === 'manual') { + if (flags & Ref) { + safelyAttachRef(finishedWork, finishedWork.return); + } + } else if (finishedWork.pendingProps.mode !== undefined) { + safelyDetachRef(finishedWork, finishedWork.return); + } } else { recursivelyTraverseLayoutEffects( finishedRoot, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 0dac9e775a7fb..410020874a316 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -1192,6 +1192,14 @@ function commitLayoutEffectOnFiber( offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden; offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; } + + if (finishedWork.pendingProps.mode === 'manual') { + if (flags & Ref) { + safelyAttachRef(finishedWork, finishedWork.return); + } + } else if (finishedWork.pendingProps.mode !== undefined) { + safelyDetachRef(finishedWork, finishedWork.return); + } } else { recursivelyTraverseLayoutEffects( finishedRoot, From cff80e27b1554c0922c807a5e2b910578b959112 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 21 Sep 2022 10:59:31 +0100 Subject: [PATCH 02/17] Schedule Ref effect unconditionally on Offscreen --- packages/react-reconciler/src/ReactFiberBeginWork.new.js | 4 +--- packages/react-reconciler/src/ReactFiberBeginWork.old.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 170d403753a1b..66c679b424646 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -684,9 +684,7 @@ function updateOffscreenComponent( const prevState: OffscreenState | null = current !== null ? current.memoizedState : null; - if (nextProps.mode === 'manual') { - markRef(current, workInProgress); - } + markRef(current, workInProgress); if ( nextProps.mode === 'hidden' || diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 2267d8f755948..e5c3f1f4cb562 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -684,9 +684,7 @@ function updateOffscreenComponent( const prevState: OffscreenState | null = current !== null ? current.memoizedState : null; - if (nextProps.mode === 'manual') { - markRef(current, workInProgress); - } + markRef(current, workInProgress); if ( nextProps.mode === 'hidden' || From af2ff27d5356d59e34caf9795153ce9f06f0239c Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 21 Sep 2022 11:18:53 +0100 Subject: [PATCH 03/17] Make sure Offscreen's ref is detached when unmounted --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 7 ++++++- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 7 ++++++- .../react-reconciler/src/__tests__/ReactOffscreen-test.js | 1 - 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 0a439875062d5..b5d346e376b01 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -1197,7 +1197,7 @@ function commitLayoutEffectOnFiber( if (flags & Ref) { safelyAttachRef(finishedWork, finishedWork.return); } - } else if (finishedWork.pendingProps.mode !== undefined) { + } else { safelyDetachRef(finishedWork, finishedWork.return); } } else { @@ -2323,6 +2323,8 @@ function commitDeletionEffectsOnFiber( offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; + safelyDetachRef(deletedFiber, nearestMountedAncestor); + recursivelyTraverseDeletionEffects( finishedRoot, nearestMountedAncestor, @@ -3055,6 +3057,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) { // Nested Offscreen tree is already hidden. Don't disappear // its effects. } else { + safelyDetachRef(finishedWork, finishedWork.return); recursivelyTraverseDisappearLayoutEffects(finishedWork); } break; @@ -3196,6 +3199,8 @@ export function reappearLayoutEffects( finishedWork, includeWorkInProgressEffects, ); + + safelyAttachRef(finishedWork, finishedWork.return); } // TODO: Check flags & Ref safelyAttachRef(finishedWork, finishedWork.return); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 410020874a316..b28f3a641d51e 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -1197,7 +1197,7 @@ function commitLayoutEffectOnFiber( if (flags & Ref) { safelyAttachRef(finishedWork, finishedWork.return); } - } else if (finishedWork.pendingProps.mode !== undefined) { + } else { safelyDetachRef(finishedWork, finishedWork.return); } } else { @@ -2323,6 +2323,8 @@ function commitDeletionEffectsOnFiber( offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; + safelyDetachRef(deletedFiber, nearestMountedAncestor); + recursivelyTraverseDeletionEffects( finishedRoot, nearestMountedAncestor, @@ -3055,6 +3057,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) { // Nested Offscreen tree is already hidden. Don't disappear // its effects. } else { + safelyDetachRef(finishedWork, finishedWork.return); recursivelyTraverseDisappearLayoutEffects(finishedWork); } break; @@ -3196,6 +3199,8 @@ export function reappearLayoutEffects( finishedWork, includeWorkInProgressEffects, ); + + safelyAttachRef(finishedWork, finishedWork.return); } // TODO: Check flags & Ref safelyAttachRef(finishedWork, finishedWork.return); diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index ca505c785fe48..eac4eff6d85b6 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -1430,7 +1430,6 @@ describe('ReactOffscreen', () => { }); expect(offscreenRef.current).not.toBeNull(); - await act(async () => { root.render(); }); From f66da7536f97d6e7000d11308291adb257e575a0 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 21 Sep 2022 17:42:38 +0100 Subject: [PATCH 04/17] Make sure ref is mounted/unmounted in all scenarious --- .../src/ReactFiberCommitWork.new.js | 13 ------------- .../src/ReactFiberCommitWork.old.js | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index b5d346e376b01..343cc152d735c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -1192,14 +1192,6 @@ function commitLayoutEffectOnFiber( offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden; offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; } - - if (finishedWork.pendingProps.mode === 'manual') { - if (flags & Ref) { - safelyAttachRef(finishedWork, finishedWork.return); - } - } else { - safelyDetachRef(finishedWork, finishedWork.return); - } } else { recursivelyTraverseLayoutEffects( finishedRoot, @@ -2323,8 +2315,6 @@ function commitDeletionEffectsOnFiber( offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; - safelyDetachRef(deletedFiber, nearestMountedAncestor); - recursivelyTraverseDeletionEffects( finishedRoot, nearestMountedAncestor, @@ -3057,7 +3047,6 @@ export function disappearLayoutEffects(finishedWork: Fiber) { // Nested Offscreen tree is already hidden. Don't disappear // its effects. } else { - safelyDetachRef(finishedWork, finishedWork.return); recursivelyTraverseDisappearLayoutEffects(finishedWork); } break; @@ -3199,8 +3188,6 @@ export function reappearLayoutEffects( finishedWork, includeWorkInProgressEffects, ); - - safelyAttachRef(finishedWork, finishedWork.return); } // TODO: Check flags & Ref safelyAttachRef(finishedWork, finishedWork.return); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index b28f3a641d51e..0dac9e775a7fb 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -1192,14 +1192,6 @@ function commitLayoutEffectOnFiber( offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden; offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; } - - if (finishedWork.pendingProps.mode === 'manual') { - if (flags & Ref) { - safelyAttachRef(finishedWork, finishedWork.return); - } - } else { - safelyDetachRef(finishedWork, finishedWork.return); - } } else { recursivelyTraverseLayoutEffects( finishedRoot, @@ -2323,8 +2315,6 @@ function commitDeletionEffectsOnFiber( offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; - safelyDetachRef(deletedFiber, nearestMountedAncestor); - recursivelyTraverseDeletionEffects( finishedRoot, nearestMountedAncestor, @@ -3057,7 +3047,6 @@ export function disappearLayoutEffects(finishedWork: Fiber) { // Nested Offscreen tree is already hidden. Don't disappear // its effects. } else { - safelyDetachRef(finishedWork, finishedWork.return); recursivelyTraverseDisappearLayoutEffects(finishedWork); } break; @@ -3199,8 +3188,6 @@ export function reappearLayoutEffects( finishedWork, includeWorkInProgressEffects, ); - - safelyAttachRef(finishedWork, finishedWork.return); } // TODO: Check flags & Ref safelyAttachRef(finishedWork, finishedWork.return); From 45b86bf9c6ea9e1d6036af08cfd087ba8a086beb Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 14 Sep 2022 14:09:26 +0100 Subject: [PATCH 05/17] Add detach to Offscreen component --- .../react-reconciler/src/ReactFiber.new.js | 3 + .../react-reconciler/src/ReactFiber.old.js | 3 + .../src/ReactFiberBeginWork.new.js | 5 +- .../src/ReactFiberBeginWork.old.js | 5 +- .../src/ReactFiberCommitWork.new.js | 33 ++- .../src/ReactFiberCommitWork.old.js | 33 ++- .../src/ReactFiberCompleteWork.new.js | 11 +- .../src/ReactFiberCompleteWork.old.js | 11 +- .../src/ReactFiberOffscreenComponent.js | 11 +- .../src/ReactFiberWorkLoop.new.js | 2 +- .../src/ReactFiberWorkLoop.old.js | 2 +- .../src/__tests__/ReactOffscreen-test.js | 231 ++++++++++++++++++ 12 files changed, 336 insertions(+), 14 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index 57e10f9af2e31..008fd4cc5fbaa 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -74,6 +74,7 @@ import { import {OffscreenVisible} from './ReactFiberOffscreenComponent'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; +import {detachOffscreenInstance} from './ReactFiberCommitWork.new'; import {isDevToolsPresent} from './ReactFiberDevToolsHook.new'; import { resolveClassForHotReloading, @@ -755,6 +756,7 @@ export function createFiberFromOffscreen( _pendingMarkers: null, _retryCache: null, _transitions: null, + detach: () => detachOffscreenInstance(primaryChildInstance), }; fiber.stateNode = primaryChildInstance; return fiber; @@ -776,6 +778,7 @@ export function createFiberFromLegacyHidden( _pendingMarkers: null, _transitions: null, _retryCache: null, + detach: () => detachOffscreenInstance(instance), }; fiber.stateNode = instance; return fiber; diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index dac93beeec4c8..312dcfeb13cbd 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -74,6 +74,7 @@ import { import {OffscreenVisible} from './ReactFiberOffscreenComponent'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; +import {detachOffscreenInstance} from './ReactFiberCommitWork.old'; import {isDevToolsPresent} from './ReactFiberDevToolsHook.old'; import { resolveClassForHotReloading, @@ -755,6 +756,7 @@ export function createFiberFromOffscreen( _pendingMarkers: null, _retryCache: null, _transitions: null, + detach: () => detachOffscreenInstance(primaryChildInstance), }; fiber.stateNode = primaryChildInstance; return fiber; @@ -776,6 +778,7 @@ export function createFiberFromLegacyHidden( _pendingMarkers: null, _transitions: null, _retryCache: null, + detach: () => detachOffscreenInstance(instance), }; fiber.stateNode = instance; return fiber; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 66c679b424646..783e2a8fa9225 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -37,7 +37,6 @@ import type { import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new'; import type {RootState} from './ReactFiberRoot.new'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new'; - import checkPropTypes from 'shared/checkPropTypes'; import { markComponentRenderStarted, @@ -688,7 +687,9 @@ function updateOffscreenComponent( if ( nextProps.mode === 'hidden' || - (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding') + (enableLegacyHidden && + nextProps.mode === 'unstable-defer-without-hiding') || + workInProgress.stateNode._visibility & OffscreenDetached ) { // Rendering a hidden tree. diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index e5c3f1f4cb562..6ea084d81a6a4 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -37,7 +37,6 @@ import type { import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old'; import type {RootState} from './ReactFiberRoot.old'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old'; - import checkPropTypes from 'shared/checkPropTypes'; import { markComponentRenderStarted, @@ -688,7 +687,9 @@ function updateOffscreenComponent( if ( nextProps.mode === 'hidden' || - (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding') + (enableLegacyHidden && + nextProps.mode === 'unstable-defer-without-hiding') || + workInProgress.stateNode._visibility & OffscreenDetached ) { // Rendering a hidden tree. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 343cc152d735c..97db102c2aca4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -31,6 +31,7 @@ import type { import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.new'; import type {RootState} from './ReactFiberRoot.new'; +import {scheduleMicrotask} from './ReactFiberHostConfig'; import type { Transition, TracingMarkerInstance, @@ -169,6 +170,7 @@ import { setIsRunningInsertionEffect, getExecutionContext, CommitContext, + RenderContext, NoContext, } from './ReactFiberWorkLoop.new'; import { @@ -197,6 +199,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.new'; import {clearTransitionsForLanes} from './ReactFiberLane.new'; import { OffscreenVisible, + OffscreenDetached, OffscreenPassiveEffectsConnected, } from './ReactFiberOffscreenComponent'; import { @@ -1154,7 +1157,9 @@ function commitLayoutEffectOnFiber( case OffscreenComponent: { const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; if (isModernRoot) { - const isHidden = finishedWork.memoizedState !== null; + const isHidden = + finishedWork.memoizedState !== null || + finishedWork.stateNode._visibility & OffscreenDetached; const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; if (newOffscreenSubtreeIsHidden) { @@ -2426,6 +2431,26 @@ function getRetryCache(finishedWork) { } } +export function detachOffscreenInstance(instance: OffscreenInstance): void { + const currentOffscreenFiber = instance._current; + if (currentOffscreenFiber === null) { + throw new Error('TODO: error message'); + } + + const executionContext = getExecutionContext(); + if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { + scheduleMicrotask(() => { + instance._visibility |= OffscreenDetached; + disappearLayoutEffects(currentOffscreenFiber); + disconnectPassiveEffect(currentOffscreenFiber); + }); + } else { + instance._visibility |= OffscreenDetached; + disappearLayoutEffects(currentOffscreenFiber); + disconnectPassiveEffect(currentOffscreenFiber); + } +} + function attachSuspenseRetryListeners( finishedWork: Fiber, wakeables: Set, @@ -2842,6 +2867,7 @@ function commitMutationEffectsOnFiber( } commitReconciliationEffects(finishedWork); + finishedWork.stateNode._current = finishedWork; if (flags & Visibility) { const offscreenInstance: OffscreenInstance = finishedWork.stateNode; @@ -2868,7 +2894,10 @@ function commitMutationEffectsOnFiber( } } - if (supportsMutation) { + if ( + supportsMutation && + !(offscreenInstance._visibility & OffscreenDetached) + ) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 0dac9e775a7fb..7cfd0dc22dad9 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -31,6 +31,7 @@ import type { import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.old'; import type {RootState} from './ReactFiberRoot.old'; +import {scheduleMicrotask} from './ReactFiberHostConfig'; import type { Transition, TracingMarkerInstance, @@ -169,6 +170,7 @@ import { setIsRunningInsertionEffect, getExecutionContext, CommitContext, + RenderContext, NoContext, } from './ReactFiberWorkLoop.old'; import { @@ -197,6 +199,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.old'; import {clearTransitionsForLanes} from './ReactFiberLane.old'; import { OffscreenVisible, + OffscreenDetached, OffscreenPassiveEffectsConnected, } from './ReactFiberOffscreenComponent'; import { @@ -1154,7 +1157,9 @@ function commitLayoutEffectOnFiber( case OffscreenComponent: { const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; if (isModernRoot) { - const isHidden = finishedWork.memoizedState !== null; + const isHidden = + finishedWork.memoizedState !== null || + finishedWork.stateNode._visibility & OffscreenDetached; const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; if (newOffscreenSubtreeIsHidden) { @@ -2426,6 +2431,26 @@ function getRetryCache(finishedWork) { } } +export function detachOffscreenInstance(instance: OffscreenInstance): void { + const currentOffscreenFiber = instance._current; + if (currentOffscreenFiber === null) { + throw new Error('TODO: error message'); + } + + const executionContext = getExecutionContext(); + if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { + scheduleMicrotask(() => { + instance._visibility |= OffscreenDetached; + disappearLayoutEffects(currentOffscreenFiber); + disconnectPassiveEffect(currentOffscreenFiber); + }); + } else { + instance._visibility |= OffscreenDetached; + disappearLayoutEffects(currentOffscreenFiber); + disconnectPassiveEffect(currentOffscreenFiber); + } +} + function attachSuspenseRetryListeners( finishedWork: Fiber, wakeables: Set, @@ -2842,6 +2867,7 @@ function commitMutationEffectsOnFiber( } commitReconciliationEffects(finishedWork); + finishedWork.stateNode._current = finishedWork; if (flags & Visibility) { const offscreenInstance: OffscreenInstance = finishedWork.stateNode; @@ -2868,7 +2894,10 @@ function commitMutationEffectsOnFiber( } } - if (supportsMutation) { + if ( + supportsMutation && + !(offscreenInstance._visibility & OffscreenDetached) + ) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 195a6f8d54ffc..3b5373004ecd4 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -28,6 +28,7 @@ import type { SuspenseListRenderState, } from './ReactFiberSuspenseComponent.new'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; +import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new'; import type {Cache} from './ReactFiberCacheComponent.new'; import { @@ -428,7 +429,15 @@ if (supportsMutation) { if (child !== null) { child.return = node; } - appendAllChildrenToContainer(containerChildSet, node, true, true); + // Detached tree is hidden from user space. + const _needsVisibilityToggle = + node.stateNode._visibility & OffscreenDetached; + appendAllChildrenToContainer( + containerChildSet, + node, + _needsVisibilityToggle, + true, + ); } else if (node.child !== null) { node.child.return = node; node = node.child; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 1ead0a8902311..15ab4a059b39b 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -28,6 +28,7 @@ import type { SuspenseListRenderState, } from './ReactFiberSuspenseComponent.old'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; +import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old'; import type {Cache} from './ReactFiberCacheComponent.old'; import { @@ -428,7 +429,15 @@ if (supportsMutation) { if (child !== null) { child.return = node; } - appendAllChildrenToContainer(containerChildSet, node, true, true); + // Detached tree is hidden from user space. + const _needsVisibilityToggle = + node.stateNode._visibility & OffscreenDetached; + appendAllChildrenToContainer( + containerChildSet, + node, + _needsVisibilityToggle, + true, + ); } else if (node.child !== null) { node.child.return = node; node = node.child; diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index fe2817f0f4377..7281a925128fc 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -44,8 +44,9 @@ export type OffscreenQueue = { type OffscreenVisibility = number; -export const OffscreenVisible = /* */ 0b01; -export const OffscreenPassiveEffectsConnected = /* */ 0b10; +export const OffscreenVisible = /* */ 0b001; +export const OffscreenDetached = /* */ 0b010; +export const OffscreenPassiveEffectsConnected = /* */ 0b100; export type OffscreenInstance = { _visibility: OffscreenVisibility, @@ -53,4 +54,10 @@ export type OffscreenInstance = { _transitions: Set | null, // $FlowFixMe[incompatible-type-arg] found when upgrading Flow _retryCache: WeakSet | Set | null, + + // Represents the current Offscreen fiber + _current: Fiber | null, + detach: () => void, + + // TODO: attach }; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 38994cdef5291..ed2f11c705687 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -290,7 +290,7 @@ type ExecutionContext = number; export const NoContext = /* */ 0b000; const BatchedContext = /* */ 0b001; -const RenderContext = /* */ 0b010; +export const RenderContext = /* */ 0b010; export const CommitContext = /* */ 0b100; type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 1dacb1fd5685e..bf7a4b02e7321 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -290,7 +290,7 @@ type ExecutionContext = number; export const NoContext = /* */ 0b000; const BatchedContext = /* */ 0b001; -const RenderContext = /* */ 0b010; +export const RenderContext = /* */ 0b010; export const CommitContext = /* */ 0b100; type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6; diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index eac4eff6d85b6..ede7a5a2eff57 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -1362,6 +1362,200 @@ describe('ReactOffscreen', () => { expect(offscreenRef.current).not.toBeNull(); }); + + // @gate enableOffscreen + it('should lower update priority for detached Offscreen', async () => { + let updateChildState; + let updateHighPriorityComponentState; + let offscreenRef; + + function Child() { + const [state, _stateUpdate] = useState(0); + updateChildState = _stateUpdate; + const text = 'Child ' + state; + return ; + } + + function HighPriorityComponent(props) { + const [state, _stateUpdate] = useState(0); + updateHighPriorityComponentState = _stateUpdate; + const text = 'HighPriorityComponent ' + state; + return ( + <> + + {props.children} + + ); + } + + function App() { + offscreenRef = useRef(null); + return ( + <> + + + + + + + ); + } + + const root = ReactNoop.createRoot(); + + await act(async () => { + root.render(); + }); + + expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + + expect(offscreenRef.current).not.toBeNull(); + expect(offscreenRef.current.detach).not.toBeNull(); + + // Offscreen is attached by default. State updates from offscreen are **not defered**. + await act(async () => { + updateChildState(1); + updateHighPriorityComponentState(1); + expect(Scheduler).toFlushUntilNextPaint([ + 'HighPriorityComponent 1', + 'Child 1', + ]); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + + // detaching offscreen. + offscreenRef.current.detach(); + + // Offscreen is detached. State updates from offscreen are **defered**. + await act(async () => { + updateChildState(2); + updateHighPriorityComponentState(2); + expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + + expect(Scheduler).toHaveYielded(['Child 2']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + + // @gate enableOffscreen + it('defers detachment if called during commit', async () => { + let updateChildState; + let updateHighPriorityComponentState; + let offscreenRef; + let nextRenderTriggerDetach = false; + + function Child() { + const [state, _stateUpdate] = useState(0); + updateChildState = _stateUpdate; + const text = 'Child ' + state; + return ; + } + + function HighPriorityComponent(props) { + const [state, _stateUpdate] = useState(0); + updateHighPriorityComponentState = _stateUpdate; + const text = 'HighPriorityComponent ' + state; + useLayoutEffect(() => { + if (nextRenderTriggerDetach) { + offscreenRef.current.detach(); + _stateUpdate(state + 1); + updateChildState(state + 1); + nextRenderTriggerDetach = false; + } + }); + return ( + <> + + {props.children} + + ); + } + + function App() { + offscreenRef = useRef(null); + return ( + <> + + + + + + + ); + } + + const root = ReactNoop.createRoot(); + + await act(async () => { + root.render(); + }); + + expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']); + + nextRenderTriggerDetach = true; + + // Offscreen is attached. State updates from offscreen are **not defered**. + // Offscreen is detached inside useLayoutEffect; + await act(async () => { + updateChildState(1); + updateHighPriorityComponentState(1); + expect(Scheduler).toFlushUntilNextPaint([ + 'HighPriorityComponent 1', + 'Child 1', + 'HighPriorityComponent 2', + 'Child 2', + ]); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + + // Offscreen is detached. State updates from offscreen are **defered**. + await act(async () => { + updateChildState(3); + updateHighPriorityComponentState(3); + expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 3']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + + expect(Scheduler).toHaveYielded(['Child 3']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); }); // @gate enableOffscreen @@ -1437,5 +1631,42 @@ describe('ReactOffscreen', () => { expect(offscreenRef.current).toBeNull(); }); + // @gate enableOffscreen + it('should change _current', async () => { + let offscreenRef; + const root = ReactNoop.createRoot(); + + function App({children}) { + offscreenRef = useRef(null); + return ( + + {children} + + ); + } + + await act(async () => { + root.render( + +
+ , + ); + }); + + expect(offscreenRef.current).not.toBeNull(); + const firstFiber = offscreenRef.current; + + console.log('about to render second time'); + await act(async () => { + root.render( + + + , + ); + }); + + expect(offscreenRef.current._current === firstFiber._current).toBeFalsy(); + }); + // TODO: When attach/detach methods are implemented. Add tests for nested Offscreen case. }); From 5d1e6ebc8316cbc0adf0b7c18658d15ae1e3732c Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Fri, 30 Sep 2022 16:03:25 +0100 Subject: [PATCH 06/17] Add todos and proper error message --- packages/react-reconciler/src/ReactFiber.new.js | 2 ++ packages/react-reconciler/src/ReactFiberBeginWork.new.js | 1 + packages/react-reconciler/src/ReactFiberCommitWork.new.js | 5 ++++- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 4 +++- scripts/error-codes/codes.json | 5 +++-- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index 008fd4cc5fbaa..c9bf1fcf5e4ec 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -756,6 +756,7 @@ export function createFiberFromOffscreen( _pendingMarkers: null, _retryCache: null, _transitions: null, + _current: null, detach: () => detachOffscreenInstance(primaryChildInstance), }; fiber.stateNode = primaryChildInstance; @@ -778,6 +779,7 @@ export function createFiberFromLegacyHidden( _pendingMarkers: null, _transitions: null, _retryCache: null, + _current: null, detach: () => detachOffscreenInstance(instance), }; fiber.stateNode = instance; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 783e2a8fa9225..0815df11bdbbf 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -689,6 +689,7 @@ function updateOffscreenComponent( nextProps.mode === 'hidden' || (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding') || + // TODO: remove read from stateNode. workInProgress.stateNode._visibility & OffscreenDetached ) { // Rendering a hidden tree. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 97db102c2aca4..9be4fb7012dbb 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2434,7 +2434,9 @@ function getRetryCache(finishedWork) { export function detachOffscreenInstance(instance: OffscreenInstance): void { const currentOffscreenFiber = instance._current; if (currentOffscreenFiber === null) { - throw new Error('TODO: error message'); + throw new Error( + 'Calling Offscreen.detach before instance handle has been set.', + ); } const executionContext = getExecutionContext(); @@ -2867,6 +2869,7 @@ function commitMutationEffectsOnFiber( } commitReconciliationEffects(finishedWork); + // TODO: Add explicit effect flag to set _current. finishedWork.stateNode._current = finishedWork; if (flags & Visibility) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 7cfd0dc22dad9..5da26dbab0211 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2434,7 +2434,9 @@ function getRetryCache(finishedWork) { export function detachOffscreenInstance(instance: OffscreenInstance): void { const currentOffscreenFiber = instance._current; if (currentOffscreenFiber === null) { - throw new Error('TODO: error message'); + throw new Error( + 'Calling Offscreen.detach before instance handle has been set.', + ); } const executionContext = getExecutionContext(); diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 278c8d464089a..60d02f0207c4c 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -440,5 +440,6 @@ "452": "React expected an element (document.documentElement) to exist in the Document but one was not found. React never removes the documentElement for any Document it renders into so the cause is likely in some other script running on this page.", "453": "React expected a element (document.head) to exist in the Document but one was not found. React never removes the head for any Document it renders into so the cause is likely in some other script running on this page.", "454": "React expected a element (document.body) to exist in the Document but one was not found. React never removes the body for any Document it renders into so the cause is likely in some other script running on this page.", - "455": "This CacheSignal was requested outside React which means that it is immediately aborted." -} \ No newline at end of file + "455": "This CacheSignal was requested outside React which means that it is immediately aborted.", + "456": "Calling Offscreen.detach before instance handle has been set.", +} From b53761c5887a9830ce801d7568831cffeb9727a8 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Fri, 30 Sep 2022 17:36:00 +0100 Subject: [PATCH 07/17] Fix flow errors --- packages/react-reconciler/src/ReactFiber.old.js | 2 ++ packages/react-reconciler/src/ReactFiberBeginWork.old.js | 1 + packages/react-reconciler/src/ReactFiberCommitWork.old.js | 1 + packages/react-reconciler/src/ReactFiberCompleteWork.new.js | 2 +- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 2 +- packages/react-reconciler/src/ReactFiberOffscreenComponent.js | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 312dcfeb13cbd..914d0a5250b5b 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -756,6 +756,7 @@ export function createFiberFromOffscreen( _pendingMarkers: null, _retryCache: null, _transitions: null, + _current: null, detach: () => detachOffscreenInstance(primaryChildInstance), }; fiber.stateNode = primaryChildInstance; @@ -778,6 +779,7 @@ export function createFiberFromLegacyHidden( _pendingMarkers: null, _transitions: null, _retryCache: null, + _current: null, detach: () => detachOffscreenInstance(instance), }; fiber.stateNode = instance; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 6ea084d81a6a4..7651b581bf7bd 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -689,6 +689,7 @@ function updateOffscreenComponent( nextProps.mode === 'hidden' || (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding') || + // TODO: remove read from stateNode. workInProgress.stateNode._visibility & OffscreenDetached ) { // Rendering a hidden tree. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 5da26dbab0211..953d63deb120c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2869,6 +2869,7 @@ function commitMutationEffectsOnFiber( } commitReconciliationEffects(finishedWork); + // TODO: Add explicit effect flag to set _current. finishedWork.stateNode._current = finishedWork; if (flags & Visibility) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 3b5373004ecd4..70da28be0a108 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -431,7 +431,7 @@ if (supportsMutation) { } // Detached tree is hidden from user space. const _needsVisibilityToggle = - node.stateNode._visibility & OffscreenDetached; + (node.stateNode._visibility & OffscreenDetached) !== 0; appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 15ab4a059b39b..d456ba8e25d8c 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -431,7 +431,7 @@ if (supportsMutation) { } // Detached tree is hidden from user space. const _needsVisibilityToggle = - node.stateNode._visibility & OffscreenDetached; + (node.stateNode._visibility & OffscreenDetached) !== 0; appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index 7281a925128fc..3ed1901795299 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -10,6 +10,7 @@ import type {ReactNodeList, OffscreenMode, Wakeable} from 'shared/ReactTypes'; import type {Lanes} from './ReactFiberLane.old'; import type {SpawnedCachePool} from './ReactFiberCacheComponent.new'; +import type {Fiber} from './ReactInternalTypes'; import type { Transition, TracingMarkerInstance, From eba887fe3d4811fd33c6bf01287e871166bff5e8 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Fri, 30 Sep 2022 18:26:04 +0100 Subject: [PATCH 08/17] Fix tests --- .../react-reconciler/src/__tests__/ReactOffscreen-test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index ede7a5a2eff57..66bd613763960 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -1654,9 +1654,8 @@ describe('ReactOffscreen', () => { }); expect(offscreenRef.current).not.toBeNull(); - const firstFiber = offscreenRef.current; + const firstFiber = offscreenRef.current._current; - console.log('about to render second time'); await act(async () => { root.render( @@ -1665,7 +1664,7 @@ describe('ReactOffscreen', () => { ); }); - expect(offscreenRef.current._current === firstFiber._current).toBeFalsy(); + expect(offscreenRef.current._current === firstFiber).toBeFalsy(); }); // TODO: When attach/detach methods are implemented. Add tests for nested Offscreen case. From d5900e80a9734eccab0613b5c6520017ba6b2478 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 15:20:52 +0100 Subject: [PATCH 09/17] Limit use of stateNode to beginWork only --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 4 +--- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 4 +--- packages/react-reconciler/src/ReactFiberCompleteWork.new.js | 4 +--- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 9be4fb7012dbb..fa2f1cf6863e2 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -1157,9 +1157,7 @@ function commitLayoutEffectOnFiber( case OffscreenComponent: { const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; if (isModernRoot) { - const isHidden = - finishedWork.memoizedState !== null || - finishedWork.stateNode._visibility & OffscreenDetached; + const isHidden = finishedWork.memoizedState !== null; const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; if (newOffscreenSubtreeIsHidden) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 953d63deb120c..aff122deda211 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -1157,9 +1157,7 @@ function commitLayoutEffectOnFiber( case OffscreenComponent: { const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; if (isModernRoot) { - const isHidden = - finishedWork.memoizedState !== null || - finishedWork.stateNode._visibility & OffscreenDetached; + const isHidden = finishedWork.memoizedState !== null; const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; if (newOffscreenSubtreeIsHidden) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 70da28be0a108..4a6dc48462641 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -28,7 +28,6 @@ import type { SuspenseListRenderState, } from './ReactFiberSuspenseComponent.new'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; -import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new'; import type {Cache} from './ReactFiberCacheComponent.new'; import { @@ -430,8 +429,7 @@ if (supportsMutation) { child.return = node; } // Detached tree is hidden from user space. - const _needsVisibilityToggle = - (node.stateNode._visibility & OffscreenDetached) !== 0; + const _needsVisibilityToggle = node.memoizedState !== null; appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index d456ba8e25d8c..31c75755c46b7 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -28,7 +28,6 @@ import type { SuspenseListRenderState, } from './ReactFiberSuspenseComponent.old'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; -import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old'; import type {Cache} from './ReactFiberCacheComponent.old'; import { @@ -430,8 +429,7 @@ if (supportsMutation) { child.return = node; } // Detached tree is hidden from user space. - const _needsVisibilityToggle = - (node.stateNode._visibility & OffscreenDetached) !== 0; + const _needsVisibilityToggle = node.memoizedState !== null; appendAllChildrenToContainer( containerChildSet, node, From 5d5464182a992ae547d877ed545c99dd3fe6ae33 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 15:27:05 +0100 Subject: [PATCH 10/17] Check for Offscreen mode instead of _visibility --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 6 ++---- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index fa2f1cf6863e2..c5f1789a3a5da 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2895,10 +2895,8 @@ function commitMutationEffectsOnFiber( } } - if ( - supportsMutation && - !(offscreenInstance._visibility & OffscreenDetached) - ) { + // Offscreen with manual mode manages visibility manually. + if (supportsMutation && finishedWork.memoizedProps.mode !== 'manual') { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index aff122deda211..96512b3f9838c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2895,10 +2895,8 @@ function commitMutationEffectsOnFiber( } } - if ( - supportsMutation && - !(offscreenInstance._visibility & OffscreenDetached) - ) { + // Offscreen with manual mode manages visibility manually. + if (supportsMutation && finishedWork.memoizedProps.mode !== 'manual') { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); From efb94b445e39118a725429f81e508f7d12ace67f Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 15:38:30 +0100 Subject: [PATCH 11/17] Add null check for memoizedProps --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 2 +- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index c5f1789a3a5da..41803b182a061 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2896,7 +2896,7 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - if (supportsMutation && finishedWork.memoizedProps.mode !== 'manual') { + if (supportsMutation && finishedWork.pendingProps.mode !== 'manual') { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 96512b3f9838c..480a0cd04249c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2896,7 +2896,7 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - if (supportsMutation && finishedWork.memoizedProps.mode !== 'manual') { + if (supportsMutation && finishedWork.pendingProps.mode !== 'manual') { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); From d4bff72db8c9864f227af637c0518010962ac26a Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 15:57:34 +0100 Subject: [PATCH 12/17] Fix persistent mode tests --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 5 ++++- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 5 ++++- packages/react-reconciler/src/ReactFiberCompleteWork.new.js | 4 ++-- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 41803b182a061..0f776232aa7e8 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2896,7 +2896,10 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - if (supportsMutation && finishedWork.pendingProps.mode !== 'manual') { + const shouldControlChildrenVisibility = + finishedWork.memoizedProps === null || + finishedWork.memoizedProps.mode !== 'manual'; + if (supportsMutation && shouldControlChildrenVisibility) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 480a0cd04249c..f7e3e91ea9288 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2896,7 +2896,10 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - if (supportsMutation && finishedWork.pendingProps.mode !== 'manual') { + const shouldControlChildrenVisibility = + finishedWork.memoizedProps == null || + finishedWork.memoizedProps.mode !== 'manual'; + if (supportsMutation && shouldControlChildrenVisibility) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 4a6dc48462641..6d27da92846cc 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -428,8 +428,8 @@ if (supportsMutation) { if (child !== null) { child.return = node; } - // Detached tree is hidden from user space. - const _needsVisibilityToggle = node.memoizedState !== null; + // If Offscreen is not in manual mode, detached tree is hidden from user space. + const _needsVisibilityToggle = node.memoizedProps.mode !== 'manual'; appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 31c75755c46b7..9c638606b5e01 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -428,8 +428,8 @@ if (supportsMutation) { if (child !== null) { child.return = node; } - // Detached tree is hidden from user space. - const _needsVisibilityToggle = node.memoizedState !== null; + // If Offscreen is not in manual mode, detached tree is hidden from user space. + const _needsVisibilityToggle = node.memoizedProps.mode !== 'manual'; appendAllChildrenToContainer( containerChildSet, node, From d6e302521e7a3473f4b8016253aa9535aca4f757 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 16:02:13 +0100 Subject: [PATCH 13/17] yarn replace-fork --- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index f7e3e91ea9288..503531c64896f 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2897,7 +2897,7 @@ function commitMutationEffectsOnFiber( // Offscreen with manual mode manages visibility manually. const shouldControlChildrenVisibility = - finishedWork.memoizedProps == null || + finishedWork.memoizedProps === null || finishedWork.memoizedProps.mode !== 'manual'; if (supportsMutation && shouldControlChildrenVisibility) { // TODO: This needs to run whenever there's an insertion or update From 994c11cdfba5da3026ca50dff6273a932771fe89 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 16:03:42 +0100 Subject: [PATCH 14/17] Add null check --- packages/react-reconciler/src/ReactFiberCompleteWork.new.js | 3 ++- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 6d27da92846cc..90f0d560ed868 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -429,7 +429,8 @@ if (supportsMutation) { child.return = node; } // If Offscreen is not in manual mode, detached tree is hidden from user space. - const _needsVisibilityToggle = node.memoizedProps.mode !== 'manual'; + const _needsVisibilityToggle = + node.memoizedProps === null || node.memoizedProps.mode !== 'manual'; appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 9c638606b5e01..60613da786adb 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -429,7 +429,8 @@ if (supportsMutation) { child.return = node; } // If Offscreen is not in manual mode, detached tree is hidden from user space. - const _needsVisibilityToggle = node.memoizedProps.mode !== 'manual'; + const _needsVisibilityToggle = + node.memoizedProps === null || node.memoizedProps.mode !== 'manual'; appendAllChildrenToContainer( containerChildSet, node, From 86d6fe88fec1dbfaa6ddd777fc90ad64bc895c5e Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 Oct 2022 18:30:14 +0100 Subject: [PATCH 15/17] Flow --- packages/react-reconciler/src/ReactFiberBeginWork.new.js | 1 + packages/react-reconciler/src/ReactFiberBeginWork.old.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 0815df11bdbbf..1f7d5c304c678 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -29,6 +29,7 @@ import type { OffscreenQueue, OffscreenInstance, } from './ReactFiberOffscreenComponent'; +import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type { Cache, CacheComponentState, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 7651b581bf7bd..b3683ba0bcd64 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -29,6 +29,7 @@ import type { OffscreenQueue, OffscreenInstance, } from './ReactFiberOffscreenComponent'; +import {OffscreenDetached} from './ReactFiberOffscreenComponent'; import type { Cache, CacheComponentState, From 2e0d8b93d9c1cbbaa475283da9e5414efef19978 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 12 Oct 2022 11:15:58 +0100 Subject: [PATCH 16/17] fix build error in yarn build-combined script --- packages/react-reconciler/src/ReactFiber.new.js | 3 +-- packages/react-reconciler/src/ReactFiber.old.js | 3 +-- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 2 +- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index c9bf1fcf5e4ec..971a88369c65a 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -73,8 +73,6 @@ import { } from './ReactWorkTags'; import {OffscreenVisible} from './ReactFiberOffscreenComponent'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; - -import {detachOffscreenInstance} from './ReactFiberCommitWork.new'; import {isDevToolsPresent} from './ReactFiberDevToolsHook.new'; import { resolveClassForHotReloading, @@ -110,6 +108,7 @@ import { REACT_TRACING_MARKER_TYPE, } from 'shared/ReactSymbols'; import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new'; +import {detachOffscreenInstance} from './ReactFiberCommitWork.new'; export type {Fiber}; diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 914d0a5250b5b..48c7db420c571 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -73,8 +73,6 @@ import { } from './ReactWorkTags'; import {OffscreenVisible} from './ReactFiberOffscreenComponent'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; - -import {detachOffscreenInstance} from './ReactFiberCommitWork.old'; import {isDevToolsPresent} from './ReactFiberDevToolsHook.old'; import { resolveClassForHotReloading, @@ -110,6 +108,7 @@ import { REACT_TRACING_MARKER_TYPE, } from 'shared/ReactSymbols'; import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.old'; +import {detachOffscreenInstance} from './ReactFiberCommitWork.old'; export type {Fiber}; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 0f776232aa7e8..93a0598dfc40c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -31,7 +31,6 @@ import type { import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.new'; import type {RootState} from './ReactFiberRoot.new'; -import {scheduleMicrotask} from './ReactFiberHostConfig'; import type { Transition, TracingMarkerInstance, @@ -154,6 +153,7 @@ import { clearSingleton, acquireSingletonInstance, releaseSingletonInstance, + scheduleMicrotask, } from './ReactFiberHostConfig'; import { captureCommitPhaseError, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 503531c64896f..305ee529fd3db 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -31,7 +31,6 @@ import type { import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.old'; import type {RootState} from './ReactFiberRoot.old'; -import {scheduleMicrotask} from './ReactFiberHostConfig'; import type { Transition, TracingMarkerInstance, @@ -154,6 +153,7 @@ import { clearSingleton, acquireSingletonInstance, releaseSingletonInstance, + scheduleMicrotask, } from './ReactFiberHostConfig'; import { captureCommitPhaseError, From eed79b5e9821ef796b95a48d5062a35513fbcc89 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 13 Oct 2022 15:07:06 +0100 Subject: [PATCH 17/17] Move Offscreen manual check to a function --- packages/react-reconciler/src/ReactFiberCommitWork.new.js | 6 ++---- packages/react-reconciler/src/ReactFiberCommitWork.old.js | 6 ++---- .../react-reconciler/src/ReactFiberCompleteWork.new.js | 4 ++-- .../react-reconciler/src/ReactFiberCompleteWork.old.js | 4 ++-- .../react-reconciler/src/ReactFiberOffscreenComponent.js | 7 +++++++ scripts/error-codes/codes.json | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 93a0598dfc40c..248698a9cd224 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -22,6 +22,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; import type {Wakeable} from 'shared/ReactTypes'; +import {isOffscreenManual} from './ReactFiberOffscreenComponent'; import type { OffscreenState, OffscreenInstance, @@ -2896,10 +2897,7 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - const shouldControlChildrenVisibility = - finishedWork.memoizedProps === null || - finishedWork.memoizedProps.mode !== 'manual'; - if (supportsMutation && shouldControlChildrenVisibility) { + if (supportsMutation && !isOffscreenManual(finishedWork)) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 305ee529fd3db..7156a4c143db9 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -22,6 +22,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.old'; import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old'; import type {Wakeable} from 'shared/ReactTypes'; +import {isOffscreenManual} from './ReactFiberOffscreenComponent'; import type { OffscreenState, OffscreenInstance, @@ -2896,10 +2897,7 @@ function commitMutationEffectsOnFiber( } // Offscreen with manual mode manages visibility manually. - const shouldControlChildrenVisibility = - finishedWork.memoizedProps === null || - finishedWork.memoizedProps.mode !== 'manual'; - if (supportsMutation && shouldControlChildrenVisibility) { + if (supportsMutation && !isOffscreenManual(finishedWork)) { // TODO: This needs to run whenever there's an insertion or update // inside a hidden Offscreen tree. hideOrUnhideAllChildren(offscreenBoundary, isHidden); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 90f0d560ed868..e85cb8e7015ab 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -27,6 +27,7 @@ import type { SuspenseState, SuspenseListRenderState, } from './ReactFiberSuspenseComponent.new'; +import {isOffscreenManual} from './ReactFiberOffscreenComponent'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new'; import type {Cache} from './ReactFiberCacheComponent.new'; @@ -429,8 +430,7 @@ if (supportsMutation) { child.return = node; } // If Offscreen is not in manual mode, detached tree is hidden from user space. - const _needsVisibilityToggle = - node.memoizedProps === null || node.memoizedProps.mode !== 'manual'; + const _needsVisibilityToggle = !isOffscreenManual(node); appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 60613da786adb..d0bcc580df7f5 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -27,6 +27,7 @@ import type { SuspenseState, SuspenseListRenderState, } from './ReactFiberSuspenseComponent.old'; +import {isOffscreenManual} from './ReactFiberOffscreenComponent'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old'; import type {Cache} from './ReactFiberCacheComponent.old'; @@ -429,8 +430,7 @@ if (supportsMutation) { child.return = node; } // If Offscreen is not in manual mode, detached tree is hidden from user space. - const _needsVisibilityToggle = - node.memoizedProps === null || node.memoizedProps.mode !== 'manual'; + const _needsVisibilityToggle = !isOffscreenManual(node); appendAllChildrenToContainer( containerChildSet, node, diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index 3ed1901795299..dbebee78c0461 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -62,3 +62,10 @@ export type OffscreenInstance = { // TODO: attach }; + +export function isOffscreenManual(offscreenFiber: Fiber): boolean { + return ( + offscreenFiber.memoizedProps !== null && + offscreenFiber.memoizedProps.mode === 'manual' + ); +} diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 60d02f0207c4c..86c99af5d2e8d 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -441,5 +441,5 @@ "453": "React expected a element (document.head) to exist in the Document but one was not found. React never removes the head for any Document it renders into so the cause is likely in some other script running on this page.", "454": "React expected a element (document.body) to exist in the Document but one was not found. React never removes the body for any Document it renders into so the cause is likely in some other script running on this page.", "455": "This CacheSignal was requested outside React which means that it is immediately aborted.", - "456": "Calling Offscreen.detach before instance handle has been set.", + "456": "Calling Offscreen.detach before instance handle has been set." }