Skip to content

Commit 16445f1

Browse files
committed
Add detach to Offscreen component
1 parent 4320341 commit 16445f1

12 files changed

+338
-14
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
6565
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
6666

67+
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
6768
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
6869
import {
6970
resolveClassForHotReloading,
@@ -723,6 +724,7 @@ export function createFiberFromOffscreen(
723724
_pendingMarkers: null,
724725
_retryCache: null,
725726
_transitions: null,
727+
detach: () => detachOffscreenInstance(primaryChildInstance),
726728
};
727729
fiber.stateNode = primaryChildInstance;
728730
return fiber;
@@ -744,6 +746,7 @@ export function createFiberFromLegacyHidden(
744746
_pendingMarkers: null,
745747
_transitions: null,
746748
_retryCache: null,
749+
detach: () => detachOffscreenInstance(instance),
747750
};
748751
fiber.stateNode = instance;
749752
return fiber;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
6565
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
6666

67+
import {detachOffscreenInstance} from './ReactFiberCommitWork.old';
6768
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
6869
import {
6970
resolveClassForHotReloading,
@@ -723,6 +724,7 @@ export function createFiberFromOffscreen(
723724
_pendingMarkers: null,
724725
_retryCache: null,
725726
_transitions: null,
727+
detach: () => detachOffscreenInstance(primaryChildInstance),
726728
};
727729
fiber.stateNode = primaryChildInstance;
728730
return fiber;
@@ -744,6 +746,7 @@ export function createFiberFromLegacyHidden(
744746
_pendingMarkers: null,
745747
_transitions: null,
746748
_retryCache: null,
749+
detach: () => detachOffscreenInstance(instance),
747750
};
748751
fiber.stateNode = instance;
749752
return fiber;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
enableCPUSuspense,
4242
enableUseMutableSource,
4343
} from 'shared/ReactFeatureFlags';
44-
44+
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
4545
import checkPropTypes from 'shared/checkPropTypes';
4646
import {
4747
markComponentRenderStarted,
@@ -681,7 +681,9 @@ function updateOffscreenComponent(
681681

682682
if (
683683
nextProps.mode === 'hidden' ||
684-
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
684+
(enableLegacyHidden &&
685+
nextProps.mode === 'unstable-defer-without-hiding') ||
686+
workInProgress.stateNode._visibility & OffscreenDetached
685687
) {
686688
// Rendering a hidden tree.
687689

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
enableCPUSuspense,
4242
enableUseMutableSource,
4343
} from 'shared/ReactFeatureFlags';
44-
44+
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
4545
import checkPropTypes from 'shared/checkPropTypes';
4646
import {
4747
markComponentRenderStarted,
@@ -681,7 +681,9 @@ function updateOffscreenComponent(
681681

682682
if (
683683
nextProps.mode === 'hidden' ||
684-
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
684+
(enableLegacyHidden &&
685+
nextProps.mode === 'unstable-defer-without-hiding') ||
686+
workInProgress.stateNode._visibility & OffscreenDetached
685687
) {
686688
// Rendering a hidden tree.
687689

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
import type {HookFlags} from './ReactHookEffectTags';
3131
import type {Cache} from './ReactFiberCacheComponent.new';
3232
import type {RootState} from './ReactFiberRoot.new';
33+
import {scheduleMicrotask} from './ReactFiberHostConfig';
3334
import type {
3435
Transition,
3536
TracingMarkerInstance,
@@ -154,6 +155,7 @@ import {
154155
setIsRunningInsertionEffect,
155156
getExecutionContext,
156157
CommitContext,
158+
RenderContext,
157159
NoContext,
158160
} from './ReactFiberWorkLoop.new';
159161
import {
@@ -182,6 +184,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
182184
import {clearTransitionsForLanes} from './ReactFiberLane.new';
183185
import {
184186
OffscreenVisible,
187+
OffscreenDetached,
185188
OffscreenPassiveEffectsConnected,
186189
} from './ReactFiberOffscreenComponent';
187190
import {
@@ -1078,7 +1081,9 @@ function commitLayoutEffectOnFiber(
10781081
case OffscreenComponent: {
10791082
const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
10801083
if (isModernRoot) {
1081-
const isHidden = finishedWork.memoizedState !== null;
1084+
const isHidden =
1085+
finishedWork.memoizedState !== null ||
1086+
finishedWork.stateNode._visibility & OffscreenDetached;
10821087
const newOffscreenSubtreeIsHidden =
10831088
isHidden || offscreenSubtreeIsHidden;
10841089
if (newOffscreenSubtreeIsHidden) {
@@ -2255,6 +2260,26 @@ function getRetryCache(finishedWork) {
22552260
}
22562261
}
22572262

2263+
export function detachOffscreenInstance(instance: OffscreenInstance): void {
2264+
const currentOffscreenFiber = instance._current;
2265+
if (currentOffscreenFiber === null) {
2266+
throw new Error('TODO: error message');
2267+
}
2268+
2269+
const executionContext = getExecutionContext();
2270+
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
2271+
scheduleMicrotask(() => {
2272+
instance._visibility |= OffscreenDetached;
2273+
disappearLayoutEffects(currentOffscreenFiber);
2274+
disconnectPassiveEffect(currentOffscreenFiber);
2275+
});
2276+
} else {
2277+
instance._visibility |= OffscreenDetached;
2278+
disappearLayoutEffects(currentOffscreenFiber);
2279+
disconnectPassiveEffect(currentOffscreenFiber);
2280+
}
2281+
}
2282+
22582283
function attachSuspenseRetryListeners(
22592284
finishedWork: Fiber,
22602285
wakeables: Set<Wakeable>,
@@ -2639,6 +2664,7 @@ function commitMutationEffectsOnFiber(
26392664
}
26402665

26412666
commitReconciliationEffects(finishedWork);
2667+
finishedWork.stateNode._current = finishedWork;
26422668

26432669
if (flags & Visibility) {
26442670
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
@@ -2665,7 +2691,10 @@ function commitMutationEffectsOnFiber(
26652691
}
26662692
}
26672693

2668-
if (supportsMutation) {
2694+
if (
2695+
supportsMutation &&
2696+
!(offscreenInstance._visibility & OffscreenDetached)
2697+
) {
26692698
// TODO: This needs to run whenever there's an insertion or update
26702699
// inside a hidden Offscreen tree.
26712700
hideOrUnhideAllChildren(offscreenBoundary, isHidden);

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
import type {HookFlags} from './ReactHookEffectTags';
3131
import type {Cache} from './ReactFiberCacheComponent.old';
3232
import type {RootState} from './ReactFiberRoot.old';
33+
import {scheduleMicrotask} from './ReactFiberHostConfig';
3334
import type {
3435
Transition,
3536
TracingMarkerInstance,
@@ -154,6 +155,7 @@ import {
154155
setIsRunningInsertionEffect,
155156
getExecutionContext,
156157
CommitContext,
158+
RenderContext,
157159
NoContext,
158160
} from './ReactFiberWorkLoop.old';
159161
import {
@@ -182,6 +184,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
182184
import {clearTransitionsForLanes} from './ReactFiberLane.old';
183185
import {
184186
OffscreenVisible,
187+
OffscreenDetached,
185188
OffscreenPassiveEffectsConnected,
186189
} from './ReactFiberOffscreenComponent';
187190
import {
@@ -1078,7 +1081,9 @@ function commitLayoutEffectOnFiber(
10781081
case OffscreenComponent: {
10791082
const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
10801083
if (isModernRoot) {
1081-
const isHidden = finishedWork.memoizedState !== null;
1084+
const isHidden =
1085+
finishedWork.memoizedState !== null ||
1086+
finishedWork.stateNode._visibility & OffscreenDetached;
10821087
const newOffscreenSubtreeIsHidden =
10831088
isHidden || offscreenSubtreeIsHidden;
10841089
if (newOffscreenSubtreeIsHidden) {
@@ -2255,6 +2260,26 @@ function getRetryCache(finishedWork) {
22552260
}
22562261
}
22572262

2263+
export function detachOffscreenInstance(instance: OffscreenInstance): void {
2264+
const currentOffscreenFiber = instance._current;
2265+
if (currentOffscreenFiber === null) {
2266+
throw new Error('TODO: error message');
2267+
}
2268+
2269+
const executionContext = getExecutionContext();
2270+
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
2271+
scheduleMicrotask(() => {
2272+
instance._visibility |= OffscreenDetached;
2273+
disappearLayoutEffects(currentOffscreenFiber);
2274+
disconnectPassiveEffect(currentOffscreenFiber);
2275+
});
2276+
} else {
2277+
instance._visibility |= OffscreenDetached;
2278+
disappearLayoutEffects(currentOffscreenFiber);
2279+
disconnectPassiveEffect(currentOffscreenFiber);
2280+
}
2281+
}
2282+
22582283
function attachSuspenseRetryListeners(
22592284
finishedWork: Fiber,
22602285
wakeables: Set<Wakeable>,
@@ -2639,6 +2664,7 @@ function commitMutationEffectsOnFiber(
26392664
}
26402665

26412666
commitReconciliationEffects(finishedWork);
2667+
finishedWork.stateNode._current = finishedWork;
26422668

26432669
if (flags & Visibility) {
26442670
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
@@ -2665,7 +2691,10 @@ function commitMutationEffectsOnFiber(
26652691
}
26662692
}
26672693

2668-
if (supportsMutation) {
2694+
if (
2695+
supportsMutation &&
2696+
!(offscreenInstance._visibility & OffscreenDetached)
2697+
) {
26692698
// TODO: This needs to run whenever there's an insertion or update
26702699
// inside a hidden Offscreen tree.
26712700
hideOrUnhideAllChildren(offscreenBoundary, isHidden);

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
SuspenseListRenderState,
2929
} from './ReactFiberSuspenseComponent.new';
3030
import type {OffscreenState} from './ReactFiberOffscreenComponent';
31+
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
3132
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
3233
import type {Cache} from './ReactFiberCacheComponent.new';
3334
import {
@@ -410,7 +411,15 @@ if (supportsMutation) {
410411
if (child !== null) {
411412
child.return = node;
412413
}
413-
appendAllChildrenToContainer(containerChildSet, node, true, true);
414+
// Detached tree is hidden from user space.
415+
const _needsVisibilityToggle =
416+
node.stateNode._visibility & OffscreenDetached;
417+
appendAllChildrenToContainer(
418+
containerChildSet,
419+
node,
420+
_needsVisibilityToggle,
421+
true,
422+
);
414423
} else if (node.child !== null) {
415424
node.child.return = node;
416425
node = node.child;

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
SuspenseListRenderState,
2929
} from './ReactFiberSuspenseComponent.old';
3030
import type {OffscreenState} from './ReactFiberOffscreenComponent';
31+
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
3132
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
3233
import type {Cache} from './ReactFiberCacheComponent.old';
3334
import {
@@ -410,7 +411,15 @@ if (supportsMutation) {
410411
if (child !== null) {
411412
child.return = node;
412413
}
413-
appendAllChildrenToContainer(containerChildSet, node, true, true);
414+
// Detached tree is hidden from user space.
415+
const _needsVisibilityToggle =
416+
node.stateNode._visibility & OffscreenDetached;
417+
appendAllChildrenToContainer(
418+
containerChildSet,
419+
node,
420+
_needsVisibilityToggle,
421+
true,
422+
);
414423
} else if (node.child !== null) {
415424
node.child.return = node;
416425
node = node.child;

packages/react-reconciler/src/ReactFiberOffscreenComponent.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ export type OffscreenQueue = {
4444

4545
type OffscreenVisibility = number;
4646

47-
export const OffscreenVisible = /* */ 0b01;
48-
export const OffscreenPassiveEffectsConnected = /* */ 0b10;
47+
export const OffscreenVisible = /* */ 0b001;
48+
export const OffscreenDetached = /* */ 0b010;
49+
export const OffscreenPassiveEffectsConnected = /* */ 0b100;
4950

5051
export type OffscreenInstance = {
5152
_visibility: OffscreenVisibility,
5253
_pendingMarkers: Set<TracingMarkerInstance> | null,
5354
_transitions: Set<Transition> | null,
5455
_retryCache: WeakSet<Wakeable> | Set<Wakeable> | null,
56+
57+
// Represents the current Offscreen fiber
58+
_current: Fiber | null,
59+
detach: () => void,
60+
61+
// TODO: attach
5562
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ type ExecutionContext = number;
277277

278278
export const NoContext = /* */ 0b000;
279279
const BatchedContext = /* */ 0b001;
280-
const RenderContext = /* */ 0b010;
280+
export const RenderContext = /* */ 0b010;
281281
export const CommitContext = /* */ 0b100;
282282

283283
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;

0 commit comments

Comments
 (0)