From 8d68da3f7396064614f34b84881fe8833b6039ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 3 Sep 2024 16:04:24 -0400 Subject: [PATCH] [Fiber] Stash ThenableState on the Dependencies Object for Use By DevTools (#30866) This lets us track what a Component might suspend on from DevTools. We could already collect this by replaying a component's Hooks but that would be expensive to collect from a whole tree. The thenables themselves might contain useful information but mainly we'd want access to the `_debugInfo` on the thenables which might contain additional information from the server. https://github.com/facebook/react/blob/19bd26beb689e554fceb0b929dc5199be8cba594/packages/shared/ReactTypes.js#L114 In a follow up we should really do something similar in Flight to transfer `use()` on the debugInfo of that Server Component. --- packages/react-reconciler/src/ReactFiber.js | 28 +++++++++++++------ .../react-reconciler/src/ReactFiberHooks.js | 12 ++++++++ .../src/ReactFiberNewContext.js | 28 +++++++++++++------ .../src/ReactInternalTypes.js | 3 +- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index db9c4773444e1..4147f0d04e96c 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -404,10 +404,16 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { workInProgress.dependencies = currentDependencies === null ? null - : { - lanes: currentDependencies.lanes, - firstContext: currentDependencies.firstContext, - }; + : __DEV__ + ? { + lanes: currentDependencies.lanes, + firstContext: currentDependencies.firstContext, + _debugThenableState: currentDependencies._debugThenableState, + } + : { + lanes: currentDependencies.lanes, + firstContext: currentDependencies.firstContext, + }; // These will be overridden during the parent's reconciliation workInProgress.sibling = current.sibling; @@ -503,10 +509,16 @@ export function resetWorkInProgress( workInProgress.dependencies = currentDependencies === null ? null - : { - lanes: currentDependencies.lanes, - firstContext: currentDependencies.firstContext, - }; + : __DEV__ + ? { + lanes: currentDependencies.lanes, + firstContext: currentDependencies.firstContext, + _debugThenableState: currentDependencies._debugThenableState, + } + : { + lanes: currentDependencies.lanes, + firstContext: currentDependencies.firstContext, + }; if (enableProfilerTimer) { // Note: We don't reset the actualTime counts. It's useful to accumulate diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index abe15165203b8..83b9d10fbf91d 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -637,6 +637,18 @@ function finishRenderingHooks( ): void { if (__DEV__) { workInProgress._debugHookTypes = hookTypesDev; + // Stash the thenable state for use by DevTools. + if (workInProgress.dependencies === null) { + if (thenableState !== null) { + workInProgress.dependencies = { + lanes: NoLanes, + firstContext: null, + _debugThenableState: thenableState, + }; + } + } else { + workInProgress.dependencies._debugThenableState = thenableState; + } } // We can assume the previous dispatcher is always this one, since we set it diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 57004e499bc28..bf28da19ce046 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -825,10 +825,16 @@ function readContextForConsumer_withSelect( // This is the first dependency for this component. Create a new list. lastContextDependency = contextItem; - consumer.dependencies = { - lanes: NoLanes, - firstContext: contextItem, - }; + consumer.dependencies = __DEV__ + ? { + lanes: NoLanes, + firstContext: contextItem, + _debugThenableState: null, + } + : { + lanes: NoLanes, + firstContext: contextItem, + }; if (enableLazyContextPropagation) { consumer.flags |= NeedsPropagation; } @@ -869,10 +875,16 @@ function readContextForConsumer( // This is the first dependency for this component. Create a new list. lastContextDependency = contextItem; - consumer.dependencies = { - lanes: NoLanes, - firstContext: contextItem, - }; + consumer.dependencies = __DEV__ + ? { + lanes: NoLanes, + firstContext: contextItem, + _debugThenableState: null, + } + : { + lanes: NoLanes, + firstContext: contextItem, + }; if (enableLazyContextPropagation) { consumer.flags |= NeedsPropagation; } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 4549253ba79b6..871325f379385 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -37,6 +37,7 @@ import type { } from './ReactFiberTracingMarkerComponent'; import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates'; import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack'; +import type {ThenableState} from './ReactFiberThenable'; // Unwind Circular: moved from ReactFiberHooks.old export type HookType = @@ -81,7 +82,7 @@ export type Dependencies = { | ContextDependency | ContextDependencyWithSelect | null, - ... + _debugThenableState?: null | ThenableState, // DEV-only }; export type MemoCache = {