Resolve the .default export of a React.lazy as the canonical value (#34906)
For debug purposes this is the value that the `React.lazy` resolves to. It also lets us look at that value for descriptions like its name.
Sebastian Markbåge committed
Oct 19, 2025 at 11:56 UTC
ec7d9a7249e84e841fbe1e4c22e1be2c0c15dae4
1 file changed
+27
-4
packages/react/src/ReactLazy.js
+27
-4
@@ -20,6 +20,8 @@ import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
20
21
import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
22
23
+import noop from 'shared/noop';
24
+
25
const Uninitialized = -1;
26
const Pending = 0;
27
const Resolved = 1;
@@ -67,12 +69,20 @@ export type LazyComponent<T, P> = {
69
70
function lazyInitializer<T>(payload: Payload<T>): T {
71
if (payload._status === Uninitialized) {
72
+ let resolveDebugValue: (void | T) => void = (null: any);
73
+ let rejectDebugValue: mixed => void = (null: any);
74
if (__DEV__ && enableAsyncDebugInfo) {
75
const ioInfo = payload._ioInfo;
76
if (ioInfo != null) {
77
// Mark when we first kicked off the lazy request.
78
// $FlowFixMe[cannot-write]
79
ioInfo.start = ioInfo.end = performance.now();
80
+ // Stash a Promise for introspection of the value later.
81
+ // $FlowFixMe[cannot-write]
82
+ ioInfo.value = new Promise((resolve, reject) => {
83
+ resolveDebugValue = resolve;
84
+ rejectDebugValue = reject;
85
+ });
86
}
87
}
88
const ctor = payload._result;
@@ -92,12 +102,20 @@ function lazyInitializer<T>(payload: Payload<T>): T {
102
const resolved: ResolvedPayload<T> = (payload: any);
103
resolved._status = Resolved;
104
resolved._result = moduleObject;
95
- if (__DEV__) {
105
+ if (__DEV__ && enableAsyncDebugInfo) {
106
const ioInfo = payload._ioInfo;
107
if (ioInfo != null) {
108
// Mark the end time of when we resolved.
109
// $FlowFixMe[cannot-write]
110
ioInfo.end = performance.now();
111
+ // Surface the default export as the resolved "value" for debug purposes.
112
+ const debugValue =
113
+ moduleObject == null ? undefined : moduleObject.default;
114
+ resolveDebugValue(debugValue);
115
+ // $FlowFixMe
116
+ ioInfo.value.status = 'fulfilled';
117
+ // $FlowFixMe
118
+ ioInfo.value.value = debugValue;
119
}
120
// Make the thenable introspectable
121
if (thenable.status === undefined) {
@@ -124,6 +142,14 @@ function lazyInitializer<T>(payload: Payload<T>): T {
142
// Mark the end time of when we rejected.
143
// $FlowFixMe[cannot-write]
144
ioInfo.end = performance.now();
145
+ // Hide unhandled rejections.
146
+ // $FlowFixMe
147
+ ioInfo.value.then(noop, noop);
148
+ rejectDebugValue(error);
149
+ // $FlowFixMe
150
+ ioInfo.value.status = 'rejected';
151
+ // $FlowFixMe
152
+ ioInfo.value.reason = error;
153
}
154
// Make the thenable introspectable
155
if (thenable.status === undefined) {
@@ -139,9 +165,6 @@ function lazyInitializer<T>(payload: Payload<T>): T {
165
if (__DEV__ && enableAsyncDebugInfo) {
166
const ioInfo = payload._ioInfo;
167
if (ioInfo != null) {
142
- // Stash the thenable for introspection of the value later.
143
- // $FlowFixMe[cannot-write]
144
- ioInfo.value = thenable;
168
const displayName = thenable.displayName;
169
if (typeof displayName === 'string') {
170
// $FlowFixMe[cannot-write]