Repository: facebook/react
Pull request: [DevTools] Pick up suspended by info from use()

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.

Changed files:
filename: packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
patch: @@ -791,7 +791,7 @@ describe('InspectedElement', () => {
         "react_lazy": {
           "_payload": Dehydrated {
             "preview_short": {…},
-            "preview_long": {_result: () => {}, _status: -1},
+            "preview_long": {_ioInfo: {…}, _result: () => {}, _status: -1},
           },
         },
         "regexp": Dehydrated {

filename: packages/react-devtools-shared/src/backend/fiber/renderer.js
patch: @@ -8,6 +8,7 @@
  */
 
 import type {
+  Thenable,
   ReactComponentInfo,
   ReactDebugInfo,
   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(
     firstChild: Fiber,
     lastChild: null | Fiber, // non-inclusive
@@ -3400,6 +3434,7 @@ export function attach(
       }
 
       trackDebugInfoFromLazyType(fiber);
+      trackDebugInfoFromUsedThenables(fiber);
 
       if (fiber.tag === HostHoistable) {
         const nearestInstance = reconcilingParent;
@@ -4231,6 +4266,7 @@ export function attach(
     }
     try {
       trackDebugInfoFromLazyType(nextFiber);
+      trackDebugInfoFromUsedThenables(nextFiber);
 
       if (
         nextFiber.tag === HostHoistable &&