205
source: null | string | Error | ReactFunctionLocation, // always null here.
206
logCount: number, // total number of errors/warnings last seen
207
treeBaseDuration: number, // the profiled time of the last render of this subtree
208
- suspendedBy: null | Array<ReactAsyncInfo>, // not used
208
+ suspendedBy: null | Array<ReactAsyncInfo>, // only used at the root
209
suspenseNode: null | SuspenseNode,
210
data: Fiber, // one of a Fiber pair
211
};
2236
// the debugStack will be a stack frame inside the ownerInstance's source.
2237
ownerInstance.source = fiber._debugStack;
2238
}
2239
+
2240
+ let unfilteredParent = parentInstance;
2241
+ while (
2242
+ unfilteredParent !== null &&
2243
+ unfilteredParent.kind === FILTERED_FIBER_INSTANCE
2244
+ ) {
2245
+ unfilteredParent = unfilteredParent.parent;
2246
+ }
2247
+
2248
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2240
- const parentID = parentInstance
2241
- ? parentInstance.kind === FILTERED_FIBER_INSTANCE
2242
- ? // A Filtered Fiber Instance will always have a Virtual Instance as a parent.
2243
- ((parentInstance.parent: any): VirtualInstance).id
2244
- : parentInstance.id
2245
- : 0;
2249
+ const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;
2250
2251
const displayNameStringID = getStringID(displayName);
2252
2335
// the debugStack will be a stack frame inside the ownerInstance's source.
2336
ownerInstance.source = componentInfo.debugStack;
2337
}
2338
+
2339
+ let unfilteredParent = parentInstance;
2340
+ while (
2341
+ unfilteredParent !== null &&
2342
+ unfilteredParent.kind === FILTERED_FIBER_INSTANCE
2343
+ ) {
2344
+ unfilteredParent = unfilteredParent.parent;
2345
+ }
2346
+
2347
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2335
- const parentID = parentInstance
2336
- ? parentInstance.kind === FILTERED_FIBER_INSTANCE
2337
- ? // A Filtered Fiber Instance will always have a Virtual Instance as a parent.
2338
- ((parentInstance.parent: any): VirtualInstance).id
2339
- : parentInstance.id
2340
- : 0;
2348
+ const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;
2349
2350
const displayNameStringID = getStringID(displayName);
2351
2444
// we bubble that up to the nearest parent Suspense boundary that isn't in fallback mode.
2445
parentSuspenseNode = parentSuspenseNode.parent;
2446
}
2439
- const parentInstance = reconcilingParent;
2440
- if (parentInstance === null || parentSuspenseNode === null) {
2447
+ if (reconcilingParent === null || parentSuspenseNode === null) {
2448
throw new Error(
2449
'It should not be possible to have suspended data outside the root. ' +
2450
'Even suspending at the first position is still a child of the root.',
2451
);
2452
}
2453
+ // Use the nearest unfiltered parent so that there's always some component that has
2454
+ // the entry on it even if you filter, or the root if all are filtered.
2455
+ let parentInstance = reconcilingParent;
2456
+ while (
2457
+ parentInstance.kind === FILTERED_FIBER_INSTANCE &&
2458
+ parentInstance.parent !== null
2459
+ ) {
2460
+ parentInstance = parentInstance.parent;
2461
+ }
2462
+
2463
const suspenseNodeSuspendedBy = parentSuspenseNode.suspendedBy;
2464
const ioInfo = asyncInfo.awaited;
2465
let suspendedBySet = suspenseNodeSuspendedBy.get(ioInfo);
2795
// so we assume insertSuspendedBy dedupes.
2796
insertSuspendedBy(asyncInfo);
2797
}
2781
- if (previousVirtualInstance) continue;
2798
+ continue;
2799
}
2800
if (typeof debugEntry.name !== 'string') {
2801
// Not a Component. Some other Debug Info.
2910
} else if (
2911
(reconcilingParent !== null &&
2912
reconcilingParent.kind === VIRTUAL_INSTANCE) ||
2896
- fiber.tag === SuspenseComponent
2913
+ fiber.tag === SuspenseComponent ||
2914
+ fiber.tag === OffscreenComponent // Use to keep resuspended instances alive inside a SuspenseComponent.
2915
) {
2916
// If the parent is a Virtual Instance and we filtered this Fiber we include a
2917
// hidden node. We also include this if it's a Suspense boundary so we can track those
2995
aquireHostInstance(nearestInstance, fiber.stateNode);
2996
}
2997
2980
- if (fiber.tag === SuspenseComponent) {
2998
+ if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) {
2999
+ // If an Offscreen component is hidden, don't mount its children yet.
3000
+ } else if (fiber.tag === SuspenseComponent && OffscreenComponent === -1) {
3001
+ // Legacy Suspense without the Offscreen wrapper. For the modern Suspense we just handle the
3002
+ // Offscreen wrapper itself specially.
3003
const isTimedOut = fiber.memoizedState !== null;
3004
if (isTimedOut) {
3005
// Special case: if Suspense mounts in a timed-out state,
3021
}
3022
// TODO: Track SuspenseNode in resuspended trees.
3023
} else {
3002
- let primaryChild: Fiber | null = null;
3003
- const areSuspenseChildrenConditionallyWrapped =
3004
- OffscreenComponent === -1;
3005
- if (areSuspenseChildrenConditionallyWrapped) {
3006
- primaryChild = fiber.child;
3007
- } else if (fiber.child !== null) {
3008
- primaryChild = fiber.child.child;
3009
- updateTrackedPathStateBeforeMount(fiber.child, null);
3010
- }
3024
+ const primaryChild: Fiber | null = fiber.child;
3025
if (primaryChild !== null) {
3026
mountChildrenRecursively(
3027
primaryChild,
3223
virtualInstance.treeBaseDuration = treeBaseDuration;
3224
}
3225
3226
+ function addUnfilteredChildrenIDs(
3227
+ parentInstance: DevToolsInstance,
3228
+ nextChildren: Array<number>,
3229
+ ): void {
3230
+ let child: null | DevToolsInstance = parentInstance.firstChild;
3231
+ while (child !== null) {
3232
+ if (child.kind === FILTERED_FIBER_INSTANCE) {
3233
+ addUnfilteredChildrenIDs(child, nextChildren);
3234
+ } else {
3235
+ nextChildren.push(child.id);
3236
+ }
3237
+ child = child.nextSibling;
3238
+ }
3239
+ }
3240
+
3241
function recordResetChildren(
3242
parentInstance: FiberInstance | VirtualInstance,
3243
) {
3255
// This is trickier than a simple comparison though, since certain types of fibers are filtered.
3256
const nextChildren: Array<number> = [];
3257
3229
- let child: null | DevToolsInstance = parentInstance.firstChild;
3230
- while (child !== null) {
3231
- if (child.kind === FILTERED_FIBER_INSTANCE) {
3232
- for (
3233
- let innerChild: null | DevToolsInstance = parentInstance.firstChild;
3234
- innerChild !== null;
3235
- innerChild = innerChild.nextSibling
3236
- ) {
3237
- nextChildren.push((innerChild: any).id);
3238
- }
3239
- } else {
3240
- nextChildren.push(child.id);
3241
- }
3242
- child = child.nextSibling;
3243
- }
3258
+ addUnfilteredChildrenIDs(parentInstance, nextChildren);
3259
3260
const numChildren = nextChildren.length;
3261
if (numChildren < 2) {
3351
// so we assume insertSuspendedBy dedupes.
3352
insertSuspendedBy(asyncInfo);
3353
}
3339
- if (previousVirtualInstance) continue;
3354
+ continue;
3355
}
3356
if (typeof debugEntry.name !== 'string') {
3357
// Not a Component. Some other Debug Info.
3511
}
3512
if (existingInstance !== null) {
3513
// Common case. Match in the same parent.
3499
- const fiberInstance: FiberInstance = (existingInstance: any); // Only matches if it's a Fiber.
3514
+ const fiberInstance: FiberInstance | FilteredFiberInstance =
3515
+ (existingInstance: any); // Only matches if it's a Fiber.
3516
3517
// We keep track if the order of the children matches the previous order.
3518
// They are always different referentially, but if the instances line up
3620
3621
// Returns whether closest unfiltered fiber parent needs to reset its child list.
3622
function updateFiberRecursively(
3607
- fiberInstance: null | FiberInstance, // null if this should be filtered
3623
+ fiberInstance: null | FiberInstance | FilteredFiberInstance, // null if this should be filtered
3624
nextFiber: Fiber,
3625
prevFiber: Fiber,
3626
traceNearestHostComponentUpdate: boolean,
3719
aquireHostInstance(nearestInstance, nextFiber.stateNode);
3720
}
3721
3706
- const isSuspense = nextFiber.tag === SuspenseComponent;
3722
let shouldResetChildren = false;
3708
- // The behavior of timed-out Suspense trees is unique.
3723
+
3724
+ // The behavior of timed-out legacy Suspense trees is unique. Without the Offscreen wrapper.
3725
// Rather than unmount the timed out content (and possibly lose important state),
3726
// React re-parents this content within a hidden Fragment while the fallback is showing.
3727
// This behavior doesn't need to be observable in the DevTools though.
3729
// The easiest fix is to strip out the intermediate Fragment fibers,
3730
// so the Elements panel and Profiler don't need to special case them.
3731
// Suspense components only have a non-null memoizedState if they're timed-out.
3716
- const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
3717
- const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
3732
+ const isLegacySuspense =
3733
+ nextFiber.tag === SuspenseComponent && OffscreenComponent === -1;
3734
+ const prevDidTimeout =
3735
+ isLegacySuspense && prevFiber.memoizedState !== null;
3736
+ const nextDidTimeOut =
3737
+ isLegacySuspense && nextFiber.memoizedState !== null;
3738
+
3739
+ const isOffscreen = nextFiber.tag === OffscreenComponent;
3740
+ const prevWasHidden = isOffscreen && prevFiber.memoizedState !== null;
3741
+ const nextIsHidden = isOffscreen && nextFiber.memoizedState !== null;
3742
+
3743
// The logic below is inspired by the code paths in updateSuspenseComponent()
3744
// inside ReactFiberBeginWork in the React source code.
3745
if (prevDidTimeout && nextDidTimeOut) {
3806
);
3807
shouldResetChildren = true;
3808
}
3809
+ } else if (prevWasHidden && nextIsHidden) {
3810
+ // We don't update any children while they're still hidden.
3811
+ } else if (prevWasHidden && !nextIsHidden) {
3812
+ // We're revealing the hidden children. We now need to update them to the latest state.
3813
+ if (nextFiber.child !== null) {
3814
+ mountChildrenRecursively(
3815
+ nextFiber.child,
3816
+ traceNearestHostComponentUpdate,
3817
+ );
3818
+ shouldResetChildren = true;
3819
+ }
3820
+ } else if (!prevWasHidden && nextIsHidden) {
3821
+ // We're hiding the children. We really just unmount them for now.
3822
+ updateChildrenRecursively(
3823
+ null,
3824
+ prevFiber.child,
3825
+ traceNearestHostComponentUpdate,
3826
+ );
3827
+ shouldResetChildren = true;
3828
} else {
3829
// Common case: Primary -> Primary.
3830
// This is the same code path as for non-Suspense fibers.
3874
if (fiberInstance !== null) {
3875
removePreviousSuspendedBy(fiberInstance, previousSuspendedBy);
3876
3833
- let componentLogsEntry = fiberToComponentLogsMap.get(
3834
- fiberInstance.data,
3835
- );
3836
- if (componentLogsEntry === undefined && fiberInstance.data.alternate) {
3837
- componentLogsEntry = fiberToComponentLogsMap.get(
3838
- fiberInstance.data.alternate,
3877
+ if (fiberInstance.kind === FIBER_INSTANCE) {
3878
+ let componentLogsEntry = fiberToComponentLogsMap.get(
3879
+ fiberInstance.data,
3880
);
3840
- }
3841
- recordConsoleLogs(fiberInstance, componentLogsEntry);
3881
+ if (
3882
+ componentLogsEntry === undefined &&
3883
+ fiberInstance.data.alternate
3884
+ ) {
3885
+ componentLogsEntry = fiberToComponentLogsMap.get(
3886
+ fiberInstance.data.alternate,
3887
+ );
3888
+ }
3889
+ recordConsoleLogs(fiberInstance, componentLogsEntry);
3890
3843
- const isProfilingSupported =
3844
- nextFiber.hasOwnProperty('treeBaseDuration');
3845
- if (isProfilingSupported) {
3846
- recordProfilingDurations(fiberInstance, prevFiber);
3891
+ const isProfilingSupported =
3892
+ nextFiber.hasOwnProperty('treeBaseDuration');
3893
+ if (isProfilingSupported) {
3894
+ recordProfilingDurations(fiberInstance, prevFiber);
3895
+ }
3896
}
3897
}
3898
if (shouldResetChildren) {
3899
// We need to crawl the subtree for closest non-filtered Fibers
3900
// so that we can display them in a flat children set.
3852
- if (fiberInstance !== null) {
3901
+ if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
3902
recordResetChildren(fiberInstance);
3903
// We've handled the child order change for this Fiber.
3904
// Since it's included, there's no need to invalidate parent child order.