[DevTools] Pick up suspended by info from use() (#34148)

Similar to #34144 but for `use()`.

`use()` dependencies don't get added to the `fiber._debugInfo` set
because that just models the things blocking the children, and not the
Fiber component itself. This picks up any debug info from the thenable
state that we stashed onto `_debugThenableState` so that we know it used
`use()`.

<img width="593" height="425" alt="Screenshot 2025-08-09 at 4 03 40 PM"
src="https://github.com/user-attachments/assets/c7e06884-4efd-47fa-a76b-132935db6ddc"
/>

Without #34146 this doesn't pick up uninstrumented promises but after
it, it'll pick those up as well. An instrumented promise that doesn't
have anything in its debug info is not picked up. For example, if it
didn't depend on any I/O on the server.

This doesn't yet pick up the stack trace of the `use()` call. That
information is in the Hooks information but needs a follow up to extract
it.
This commit is contained in:
Sebastian Markbåge 2025-08-11 12:10:05 -04:00 committed by GitHub
parent ca292f7a57
commit d587434c35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 37 additions and 1 deletions

View File

@ -791,7 +791,7 @@ describe('InspectedElement', () => {
"react_lazy": { "react_lazy": {
"_payload": Dehydrated { "_payload": Dehydrated {
"preview_short": {}, "preview_short": {},
"preview_long": {_result: () => {}, _status: -1}, "preview_long": {_ioInfo: {}, _result: () => {}, _status: -1},
}, },
}, },
"regexp": Dehydrated { "regexp": Dehydrated {

View File

@ -8,6 +8,7 @@
*/ */
import type { import type {
Thenable,
ReactComponentInfo, ReactComponentInfo,
ReactDebugInfo, ReactDebugInfo,
ReactAsyncInfo, ReactAsyncInfo,
@ -3181,6 +3182,39 @@ export function attach(
} }
} }
function trackDebugInfoFromUsedThenables(fiber: Fiber): void {
// If a Fiber called use() in DEV mode then we may have collected _debugThenableState on
// the dependencies. If so, then this will contain the thenables passed to use().
// These won't have their debug info picked up by fiber._debugInfo since that just
// contains things suspending the children. We have to collect use() separately.
const dependencies = fiber.dependencies;
if (dependencies == null) {
return;
}
const thenableState = dependencies._debugThenableState;
if (thenableState == null) {
return;
}
// In DEV the thenableState is an inner object.
const usedThenables: any = thenableState.thenables || thenableState;
if (!Array.isArray(usedThenables)) {
return;
}
for (let i = 0; i < usedThenables.length; i++) {
const thenable: Thenable<mixed> = usedThenables[i];
const debugInfo = thenable._debugInfo;
if (debugInfo) {
for (let j = 0; j < debugInfo.length; j++) {
const debugEntry = debugInfo[i];
if (debugEntry.awaited) {
const asyncInfo: ReactAsyncInfo = (debugEntry: any);
insertSuspendedBy(asyncInfo);
}
}
}
}
}
function mountVirtualChildrenRecursively( function mountVirtualChildrenRecursively(
firstChild: Fiber, firstChild: Fiber,
lastChild: null | Fiber, // non-inclusive lastChild: null | Fiber, // non-inclusive
@ -3400,6 +3434,7 @@ export function attach(
} }
trackDebugInfoFromLazyType(fiber); trackDebugInfoFromLazyType(fiber);
trackDebugInfoFromUsedThenables(fiber);
if (fiber.tag === HostHoistable) { if (fiber.tag === HostHoistable) {
const nearestInstance = reconcilingParent; const nearestInstance = reconcilingParent;
@ -4231,6 +4266,7 @@ export function attach(
} }
try { try {
trackDebugInfoFromLazyType(nextFiber); trackDebugInfoFromLazyType(nextFiber);
trackDebugInfoFromUsedThenables(nextFiber);
if ( if (
nextFiber.tag === HostHoistable && nextFiber.tag === HostHoistable &&