@samitouri / QOS-React / commits / 8d68da3f73

[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.

Sebastian Markbåge committed Sep 3, 2024 at 16:04 UTC 8d68da3f7396064614f34b84881fe8833b6039ac
4 files changed +54 -17
packages/react-reconciler/src/ReactFiber.js
+20 -8
@@ -404,10 +404,16 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
404 workInProgress.dependencies =
405 currentDependencies === null
406 ? null
407 - : {
408 - lanes: currentDependencies.lanes,
409 - firstContext: currentDependencies.firstContext,
410 - };
407 + : __DEV__
408 + ? {
409 + lanes: currentDependencies.lanes,
410 + firstContext: currentDependencies.firstContext,
411 + _debugThenableState: currentDependencies._debugThenableState,
412 + }
413 + : {
414 + lanes: currentDependencies.lanes,
415 + firstContext: currentDependencies.firstContext,
416 + };
417
418 // These will be overridden during the parent's reconciliation
419 workInProgress.sibling = current.sibling;
@@ -503,10 +509,16 @@ export function resetWorkInProgress(
509 workInProgress.dependencies =
510 currentDependencies === null
511 ? null
506 - : {
507 - lanes: currentDependencies.lanes,
508 - firstContext: currentDependencies.firstContext,
509 - };
512 + : __DEV__
513 + ? {
514 + lanes: currentDependencies.lanes,
515 + firstContext: currentDependencies.firstContext,
516 + _debugThenableState: currentDependencies._debugThenableState,
517 + }
518 + : {
519 + lanes: currentDependencies.lanes,
520 + firstContext: currentDependencies.firstContext,
521 + };
522
523 if (enableProfilerTimer) {
524 // Note: We don't reset the actualTime counts. It's useful to accumulate
packages/react-reconciler/src/ReactFiberHooks.js
+12
@@ -637,6 +637,18 @@ function finishRenderingHooks<Props, SecondArg>(
637 ): void {
638 if (__DEV__) {
639 workInProgress._debugHookTypes = hookTypesDev;
640 + // Stash the thenable state for use by DevTools.
641 + if (workInProgress.dependencies === null) {
642 + if (thenableState !== null) {
643 + workInProgress.dependencies = {
644 + lanes: NoLanes,
645 + firstContext: null,
646 + _debugThenableState: thenableState,
647 + };
648 + }
649 + } else {
650 + workInProgress.dependencies._debugThenableState = thenableState;
651 + }
652 }
653
654 // We can assume the previous dispatcher is always this one, since we set it
packages/react-reconciler/src/ReactFiberNewContext.js
+20 -8
@@ -825,10 +825,16 @@ function readContextForConsumer_withSelect<C>(
825
826 // This is the first dependency for this component. Create a new list.
827 lastContextDependency = contextItem;
828 - consumer.dependencies = {
829 - lanes: NoLanes,
830 - firstContext: contextItem,
831 - };
828 + consumer.dependencies = __DEV__
829 + ? {
830 + lanes: NoLanes,
831 + firstContext: contextItem,
832 + _debugThenableState: null,
833 + }
834 + : {
835 + lanes: NoLanes,
836 + firstContext: contextItem,
837 + };
838 if (enableLazyContextPropagation) {
839 consumer.flags |= NeedsPropagation;
840 }
@@ -869,10 +875,16 @@ function readContextForConsumer<C>(
875
876 // This is the first dependency for this component. Create a new list.
877 lastContextDependency = contextItem;
872 - consumer.dependencies = {
873 - lanes: NoLanes,
874 - firstContext: contextItem,
875 - };
878 + consumer.dependencies = __DEV__
879 + ? {
880 + lanes: NoLanes,
881 + firstContext: contextItem,
882 + _debugThenableState: null,
883 + }
884 + : {
885 + lanes: NoLanes,
886 + firstContext: contextItem,
887 + };
888 if (enableLazyContextPropagation) {
889 consumer.flags |= NeedsPropagation;
890 }
packages/react-reconciler/src/ReactInternalTypes.js
+2 -1
@@ -37,6 +37,7 @@ import type {
37 } from './ReactFiberTracingMarkerComponent';
38 import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';
39 import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack';
40 +import type {ThenableState} from './ReactFiberThenable';
41
42 // Unwind Circular: moved from ReactFiberHooks.old
43 export type HookType =
@@ -81,7 +82,7 @@ export type Dependencies = {
82 | ContextDependency<mixed>
83 | ContextDependencyWithSelect<mixed>
84 | null,
84 - ...
85 + _debugThenableState?: null | ThenableState, // DEV-only
86 };
87
88 export type MemoCache = {