Skip to content

Commit c1d414d

Browse files
sammy-SCacdlite
andauthored
Add ref to Offscreen component (#25254)
* Expose ref to Offscreen if mode is manual * Prepend private fields on OffscreenInstance with underscore * Schedule Ref effect unconditionally on Offscreen * Make sure Offscreen's ref is detached when unmounted * Make sure ref is mounted/unmounted in all scenarious * Nit: pendingProps -> memoizedProps Co-authored-by: Andrew Clark <[email protected]>
1 parent 135e33c commit c1d414d

13 files changed

+256
-85
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -722,10 +722,10 @@ export function createFiberFromOffscreen(
722722
fiber.elementType = REACT_OFFSCREEN_TYPE;
723723
fiber.lanes = lanes;
724724
const primaryChildInstance: OffscreenInstance = {
725-
visibility: OffscreenVisible,
726-
pendingMarkers: null,
727-
retryCache: null,
728-
transitions: null,
725+
_visibility: OffscreenVisible,
726+
_pendingMarkers: null,
727+
_retryCache: null,
728+
_transitions: null,
729729
};
730730
fiber.stateNode = primaryChildInstance;
731731
return fiber;
@@ -743,10 +743,10 @@ export function createFiberFromLegacyHidden(
743743
// Adding a stateNode for legacy hidden because it's currently using
744744
// the offscreen implementation, which depends on a state node
745745
const instance: OffscreenInstance = {
746-
visibility: OffscreenVisible,
747-
pendingMarkers: null,
748-
transitions: null,
749-
retryCache: null,
746+
_visibility: OffscreenVisible,
747+
_pendingMarkers: null,
748+
_transitions: null,
749+
_retryCache: null,
750750
};
751751
fiber.stateNode = instance;
752752
return fiber;

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -722,10 +722,10 @@ export function createFiberFromOffscreen(
722722
fiber.elementType = REACT_OFFSCREEN_TYPE;
723723
fiber.lanes = lanes;
724724
const primaryChildInstance: OffscreenInstance = {
725-
visibility: OffscreenVisible,
726-
pendingMarkers: null,
727-
retryCache: null,
728-
transitions: null,
725+
_visibility: OffscreenVisible,
726+
_pendingMarkers: null,
727+
_retryCache: null,
728+
_transitions: null,
729729
};
730730
fiber.stateNode = primaryChildInstance;
731731
return fiber;
@@ -743,10 +743,10 @@ export function createFiberFromLegacyHidden(
743743
// Adding a stateNode for legacy hidden because it's currently using
744744
// the offscreen implementation, which depends on a state node
745745
const instance: OffscreenInstance = {
746-
visibility: OffscreenVisible,
747-
pendingMarkers: null,
748-
transitions: null,
749-
retryCache: null,
746+
_visibility: OffscreenVisible,
747+
_pendingMarkers: null,
748+
_transitions: null,
749+
_retryCache: null,
750750
};
751751
fiber.stateNode = instance;
752752
return fiber;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,8 @@ function updateOffscreenComponent(
677677
const prevState: OffscreenState | null =
678678
current !== null ? current.memoizedState : null;
679679

680+
markRef(current, workInProgress);
681+
680682
if (
681683
nextProps.mode === 'hidden' ||
682684
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
@@ -811,8 +813,8 @@ function updateOffscreenComponent(
811813
// We have now gone from hidden to visible, so any transitions should
812814
// be added to the stack to get added to any Offscreen/suspense children
813815
const instance: OffscreenInstance | null = workInProgress.stateNode;
814-
if (instance !== null && instance.transitions != null) {
815-
transitions = Array.from(instance.transitions);
816+
if (instance !== null && instance._transitions != null) {
817+
transitions = Array.from(instance._transitions);
816818
}
817819
}
818820

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,8 @@ function updateOffscreenComponent(
677677
const prevState: OffscreenState | null =
678678
current !== null ? current.memoizedState : null;
679679

680+
markRef(current, workInProgress);
681+
680682
if (
681683
nextProps.mode === 'hidden' ||
682684
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
@@ -811,8 +813,8 @@ function updateOffscreenComponent(
811813
// We have now gone from hidden to visible, so any transitions should
812814
// be added to the stack to get added to any Offscreen/suspense children
813815
const instance: OffscreenInstance | null = workInProgress.stateNode;
814-
if (instance !== null && instance.transitions != null) {
815-
transitions = Array.from(instance.transitions);
816+
if (instance !== null && instance._transitions != null) {
817+
transitions = Array.from(instance._transitions);
816818
}
817819
}
818820

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

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
OffscreenState,
2727
OffscreenInstance,
2828
OffscreenQueue,
29+
OffscreenProps,
2930
} from './ReactFiberOffscreenComponent';
3031
import type {HookFlags} from './ReactHookEffectTags';
3132
import type {Cache} from './ReactFiberCacheComponent.new';
@@ -1141,6 +1142,14 @@ function commitLayoutEffectOnFiber(
11411142
committedLanes,
11421143
);
11431144
}
1145+
if (flags & Ref) {
1146+
const props: OffscreenProps = finishedWork.memoizedProps;
1147+
if (props.mode === 'manual') {
1148+
safelyAttachRef(finishedWork, finishedWork.return);
1149+
} else {
1150+
safelyDetachRef(finishedWork, finishedWork.return);
1151+
}
1152+
}
11441153
break;
11451154
}
11461155
default: {
@@ -1314,7 +1323,7 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
13141323
const wasHidden = prevState !== null;
13151324
const isHidden = nextState !== null;
13161325

1317-
const pendingMarkers = offscreenInstance.pendingMarkers;
1326+
const pendingMarkers = offscreenInstance._pendingMarkers;
13181327
// If there is a name on the suspense boundary, store that in
13191328
// the pending boundaries.
13201329
let name = null;
@@ -2144,6 +2153,7 @@ function commitDeletionEffectsOnFiber(
21442153
return;
21452154
}
21462155
case OffscreenComponent: {
2156+
safelyDetachRef(deletedFiber, nearestMountedAncestor);
21472157
if (deletedFiber.mode & ConcurrentMode) {
21482158
// If this offscreen component is hidden, we already unmounted it. Before
21492159
// deleting the children, track that it's already unmounted so that we
@@ -2250,9 +2260,9 @@ function getRetryCache(finishedWork) {
22502260
}
22512261
case OffscreenComponent: {
22522262
const instance: OffscreenInstance = finishedWork.stateNode;
2253-
let retryCache = instance.retryCache;
2263+
let retryCache = instance._retryCache;
22542264
if (retryCache === null) {
2255-
retryCache = instance.retryCache = new PossiblyWeakSet();
2265+
retryCache = instance._retryCache = new PossiblyWeakSet();
22562266
}
22572267
return retryCache;
22582268
}
@@ -2623,6 +2633,12 @@ function commitMutationEffectsOnFiber(
26232633
return;
26242634
}
26252635
case OffscreenComponent: {
2636+
if (flags & Ref) {
2637+
if (current !== null) {
2638+
safelyDetachRef(current, current.return);
2639+
}
2640+
}
2641+
26262642
const newState: OffscreenState | null = finishedWork.memoizedState;
26272643
const isHidden = newState !== null;
26282644
const wasHidden = current !== null && current.memoizedState !== null;
@@ -2651,9 +2667,9 @@ function commitMutationEffectsOnFiber(
26512667
// Track the current state on the Offscreen instance so we can
26522668
// read it during an event
26532669
if (isHidden) {
2654-
offscreenInstance.visibility &= ~OffscreenVisible;
2670+
offscreenInstance._visibility &= ~OffscreenVisible;
26552671
} else {
2656-
offscreenInstance.visibility |= OffscreenVisible;
2672+
offscreenInstance._visibility |= OffscreenVisible;
26572673
}
26582674

26592675
if (isHidden) {
@@ -2838,6 +2854,9 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
28382854
break;
28392855
}
28402856
case OffscreenComponent: {
2857+
// TODO (Offscreen) Check: flags & RefStatic
2858+
safelyDetachRef(finishedWork, finishedWork.return);
2859+
28412860
const isHidden = finishedWork.memoizedState !== null;
28422861
if (isHidden) {
28432862
// Nested Offscreen tree is already hidden. Don't disappear
@@ -2985,6 +3004,8 @@ export function reappearLayoutEffects(
29853004
includeWorkInProgressEffects,
29863005
);
29873006
}
3007+
// TODO: Check flags & Ref
3008+
safelyAttachRef(finishedWork, finishedWork.return);
29883009
break;
29893010
}
29903011
default: {
@@ -3098,10 +3119,10 @@ function commitOffscreenPassiveMountEffects(
30983119
// Add all the transitions saved in the update queue during
30993120
// the render phase (ie the transitions associated with this boundary)
31003121
// into the transitions set.
3101-
if (instance.transitions === null) {
3102-
instance.transitions = new Set();
3122+
if (instance._transitions === null) {
3123+
instance._transitions = new Set();
31033124
}
3104-
instance.transitions.add(transition);
3125+
instance._transitions.add(transition);
31053126
});
31063127
}
31073128

@@ -3114,17 +3135,17 @@ function commitOffscreenPassiveMountEffects(
31143135
// caused them
31153136
if (markerTransitions !== null) {
31163137
markerTransitions.forEach(transition => {
3117-
if (instance.transitions === null) {
3118-
instance.transitions = new Set();
3119-
} else if (instance.transitions.has(transition)) {
3138+
if (instance._transitions === null) {
3139+
instance._transitions = new Set();
3140+
} else if (instance._transitions.has(transition)) {
31203141
if (markerInstance.pendingBoundaries === null) {
31213142
markerInstance.pendingBoundaries = new Map();
31223143
}
3123-
if (instance.pendingMarkers === null) {
3124-
instance.pendingMarkers = new Set();
3144+
if (instance._pendingMarkers === null) {
3145+
instance._pendingMarkers = new Set();
31253146
}
31263147

3127-
instance.pendingMarkers.add(markerInstance);
3148+
instance._pendingMarkers.add(markerInstance);
31283149
}
31293150
});
31303151
}
@@ -3139,8 +3160,8 @@ function commitOffscreenPassiveMountEffects(
31393160

31403161
// TODO: Refactor this into an if/else branch
31413162
if (!isHidden) {
3142-
instance.transitions = null;
3143-
instance.pendingMarkers = null;
3163+
instance._transitions = null;
3164+
instance._pendingMarkers = null;
31443165
}
31453166
}
31463167
}
@@ -3320,7 +3341,7 @@ function commitPassiveMountOnFiber(
33203341
const isHidden = nextState !== null;
33213342

33223343
if (isHidden) {
3323-
if (instance.visibility & OffscreenPassiveEffectsConnected) {
3344+
if (instance._visibility & OffscreenPassiveEffectsConnected) {
33243345
// The effects are currently connected. Update them.
33253346
recursivelyTraversePassiveMountEffects(
33263347
finishedRoot,
@@ -3345,7 +3366,7 @@ function commitPassiveMountOnFiber(
33453366
}
33463367
} else {
33473368
// Legacy Mode: Fire the effects even if the tree is hidden.
3348-
instance.visibility |= OffscreenPassiveEffectsConnected;
3369+
instance._visibility |= OffscreenPassiveEffectsConnected;
33493370
recursivelyTraversePassiveMountEffects(
33503371
finishedRoot,
33513372
finishedWork,
@@ -3356,7 +3377,7 @@ function commitPassiveMountOnFiber(
33563377
}
33573378
} else {
33583379
// Tree is visible
3359-
if (instance.visibility & OffscreenPassiveEffectsConnected) {
3380+
if (instance._visibility & OffscreenPassiveEffectsConnected) {
33603381
// The effects are currently connected. Update them.
33613382
recursivelyTraversePassiveMountEffects(
33623383
finishedRoot,
@@ -3368,7 +3389,7 @@ function commitPassiveMountOnFiber(
33683389
// The effects are currently disconnected. Reconnect them, while also
33693390
// firing effects inside newly mounted trees. This also applies to
33703391
// the initial render.
3371-
instance.visibility |= OffscreenPassiveEffectsConnected;
3392+
instance._visibility |= OffscreenPassiveEffectsConnected;
33723393

33733394
const includeWorkInProgressEffects =
33743395
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags;
@@ -3500,7 +3521,7 @@ export function reconnectPassiveEffects(
35003521
const isHidden = nextState !== null;
35013522

35023523
if (isHidden) {
3503-
if (instance.visibility & OffscreenPassiveEffectsConnected) {
3524+
if (instance._visibility & OffscreenPassiveEffectsConnected) {
35043525
// The effects are currently connected. Update them.
35053526
recursivelyTraverseReconnectPassiveEffects(
35063527
finishedRoot,
@@ -3526,7 +3547,7 @@ export function reconnectPassiveEffects(
35263547
}
35273548
} else {
35283549
// Legacy Mode: Fire the effects even if the tree is hidden.
3529-
instance.visibility |= OffscreenPassiveEffectsConnected;
3550+
instance._visibility |= OffscreenPassiveEffectsConnected;
35303551
recursivelyTraverseReconnectPassiveEffects(
35313552
finishedRoot,
35323553
finishedWork,
@@ -3544,7 +3565,7 @@ export function reconnectPassiveEffects(
35443565
// continue traversing the tree and firing all the effects.
35453566
//
35463567
// We do need to set the "connected" flag on the instance, though.
3547-
instance.visibility |= OffscreenPassiveEffectsConnected;
3568+
instance._visibility |= OffscreenPassiveEffectsConnected;
35483569

35493570
recursivelyTraverseReconnectPassiveEffects(
35503571
finishedRoot,
@@ -3799,7 +3820,7 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
37993820

38003821
if (
38013822
isHidden &&
3802-
instance.visibility & OffscreenPassiveEffectsConnected &&
3823+
instance._visibility & OffscreenPassiveEffectsConnected &&
38033824
// For backwards compatibility, don't unmount when a tree suspends. In
38043825
// the future we may change this to unmount after a delay.
38053826
(finishedWork.return === null ||
@@ -3809,7 +3830,7 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
38093830
// TODO: Add option or heuristic to delay before disconnecting the
38103831
// effects. Then if the tree reappears before the delay has elapsed, we
38113832
// can skip toggling the effects entirely.
3812-
instance.visibility &= ~OffscreenPassiveEffectsConnected;
3833+
instance._visibility &= ~OffscreenPassiveEffectsConnected;
38133834
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
38143835
} else {
38153836
recursivelyTraversePassiveUnmountEffects(finishedWork);
@@ -3873,8 +3894,8 @@ export function disconnectPassiveEffect(finishedWork: Fiber): void {
38733894
}
38743895
case OffscreenComponent: {
38753896
const instance: OffscreenInstance = finishedWork.stateNode;
3876-
if (instance.visibility & OffscreenPassiveEffectsConnected) {
3877-
instance.visibility &= ~OffscreenPassiveEffectsConnected;
3897+
if (instance._visibility & OffscreenPassiveEffectsConnected) {
3898+
instance._visibility &= ~OffscreenPassiveEffectsConnected;
38783899
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
38793900
} else {
38803901
// The effects are already disconnected.
@@ -4002,7 +4023,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
40024023
// We need to mark this fiber's parents as deleted
40034024
const offscreenFiber: Fiber = (current.child: any);
40044025
const instance: OffscreenInstance = offscreenFiber.stateNode;
4005-
const transitions = instance.transitions;
4026+
const transitions = instance._transitions;
40064027
if (transitions !== null) {
40074028
const abortReason = {
40084029
reason: 'suspense',

0 commit comments

Comments
 (0)