Skip to content

Commit 886b3d3

Browse files
authored
[DevTools] Show suspended by subtree from Activity to next Suspense boundary (#34438)
Stacked on #34435. This adds a method to get all suspended by filtered by a specific Instance. The purpose of this is to power the feature when you filter by Activity. This would show you the "root" within that Activity boundary. This works by selecting the nearest Suspense boundary parent and then filtering its data based on if all the instances for a given I/O info is within the Activity instance. If something suspended within the Suspense boundary but outside the Activity it's not included even if it's also suspending inside the Activity since we assume it would've already been loaded then. Right now I wire this up to be a special case when you select an Activity boundary same as when you select a Suspense boundary in the Components tab but we could also only use this when you select the root in the Suspense tab for example.
1 parent 288d428 commit 886b3d3

File tree

1 file changed

+53
-9
lines changed
  • packages/react-devtools-shared/src/backend/fiber

1 file changed

+53
-9
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5721,6 +5721,7 @@ export function attach(
57215721
57225722
function getSuspendedByOfSuspenseNode(
57235723
suspenseNode: SuspenseNode,
5724+
filterByChildInstance: null | DevToolsInstance, // only include suspended by instances in this subtree
57245725
): Array<SerializedAsyncInfo> {
57255726
// Collect all ReactAsyncInfo that was suspending this SuspenseNode but
57265727
// isn't also in any parent set.
@@ -5756,8 +5757,30 @@ export function attach(
57565757
if (set.size === 0) {
57575758
return;
57585759
}
5759-
const firstInstance: DevToolsInstance = (set.values().next().value: any);
5760-
if (firstInstance.suspendedBy !== null) {
5760+
let firstInstance: null | DevToolsInstance = null;
5761+
if (filterByChildInstance === null) {
5762+
firstInstance = (set.values().next().value: any);
5763+
} else {
5764+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
5765+
for (const childInstance of set.values()) {
5766+
if (firstInstance === null) {
5767+
firstInstance = childInstance;
5768+
}
5769+
if (
5770+
childInstance !== filterByChildInstance &&
5771+
!isChildOf(
5772+
filterByChildInstance,
5773+
childInstance,
5774+
suspenseNode.instance,
5775+
)
5776+
) {
5777+
// Something suspended on this outside the filtered instance. That means that
5778+
// it is not unique to just this filtered instance so we skip including it.
5779+
return;
5780+
}
5781+
}
5782+
}
5783+
if (firstInstance !== null && firstInstance.suspendedBy !== null) {
57615784
const asyncInfo = getAwaitInSuspendedByFromIO(
57625785
firstInstance.suspendedBy,
57635786
ioInfo,
@@ -5870,6 +5893,23 @@ export function attach(
58705893
return result;
58715894
}
58725895
5896+
function getSuspendedByOfInstanceSubtree(
5897+
devtoolsInstance: DevToolsInstance,
5898+
): Array<SerializedAsyncInfo> {
5899+
// Get everything suspending below this instance down to the next Suspense node.
5900+
// First find the parent Suspense boundary which will have accumulated everything
5901+
let suspenseParentInstance = devtoolsInstance;
5902+
while (suspenseParentInstance.suspenseNode === null) {
5903+
if (suspenseParentInstance.parent === null) {
5904+
// We don't expect to hit this. We should always find the root.
5905+
return [];
5906+
}
5907+
suspenseParentInstance = suspenseParentInstance.parent;
5908+
}
5909+
const suspenseNode: SuspenseNode = suspenseParentInstance.suspenseNode;
5910+
return getSuspendedByOfSuspenseNode(suspenseNode, devtoolsInstance);
5911+
}
5912+
58735913
const FALLBACK_THROTTLE_MS: number = 300;
58745914
58755915
function getSuspendedByRange(
@@ -6383,13 +6423,17 @@ export function attach(
63836423
fiberInstance.suspenseNode !== null
63846424
? // If this is a Suspense boundary, then we include everything in the subtree that might suspend
63856425
// this boundary down to the next Suspense boundary.
6386-
getSuspendedByOfSuspenseNode(fiberInstance.suspenseNode)
6387-
: // This set is an edge case where if you pass a promise to a Client Component into a children
6388-
// position without a Server Component as the direct parent. E.g. <div>{promise}</div>
6389-
// In this case, this becomes associated with the Client/Host Component where as normally
6390-
// you'd expect these to be associated with the Server Component that awaited the data.
6391-
// TODO: Prepend other suspense sources like css, images and use().
6392-
getSuspendedByOfInstance(fiberInstance, hooks);
6426+
getSuspendedByOfSuspenseNode(fiberInstance.suspenseNode, null)
6427+
: tag === ActivityComponent
6428+
? // For Activity components we show everything that suspends the subtree down to the next boundary
6429+
// so that you can see what suspends a Transition at that level.
6430+
getSuspendedByOfInstanceSubtree(fiberInstance)
6431+
: // This set is an edge case where if you pass a promise to a Client Component into a children
6432+
// position without a Server Component as the direct parent. E.g. <div>{promise}</div>
6433+
// In this case, this becomes associated with the Client/Host Component where as normally
6434+
// you'd expect these to be associated with the Server Component that awaited the data.
6435+
// TODO: Prepend other suspense sources like css, images and use().
6436+
getSuspendedByOfInstance(fiberInstance, hooks);
63936437
const suspendedByRange = getSuspendedByRange(
63946438
getNearestSuspenseNode(fiberInstance),
63956439
);

0 commit comments

Comments
 (0)