Skip to content

Commit 0c4e650

Browse files
committed
[Fiber] Treat unwrapping React.lazy more like a use() (#34031)
While we want to get rid of React.lazy's special wrapper type and just use a Promise for the type, we still have the wrapper. However, this is still conceptually the same as a Usable in that it should be have the same if you `use(promise)` or render a Promise as a child or type position. This PR makes it behave like a `use()` when we unwrap them. We could move to a model where it actually reaches the internal of the Lazy's Promise when it unwraps but for now I leave the lazy API signature intact by just catching the Promise and then "use()" that. This lets us align on the semantics with `use()` such as the suspense yield optimization. It also lets us warn or fork based on legacy throw-a-Promise behavior where as `React.lazy` is not deprecated. DiffTrain build for [9be531c](9be531c)
1 parent 88ca7c7 commit 0c4e650

34 files changed

+2098
-2158
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7ee7571212bc02354b852752a98b23bc90546fdf
1+
9be531cd37f5558c72f7de360eb921b0074e8544
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7ee7571212bc02354b852752a98b23bc90546fdf
1+
9be531cd37f5558c72f7de360eb921b0074e8544

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ __DEV__ &&
14341434
exports.useTransition = function () {
14351435
return resolveDispatcher().useTransition();
14361436
};
1437-
exports.version = "19.2.0-www-classic-7ee75712-20250728";
1437+
exports.version = "19.2.0-www-classic-9be531cd-20250729";
14381438
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14391439
"function" ===
14401440
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ __DEV__ &&
14341434
exports.useTransition = function () {
14351435
return resolveDispatcher().useTransition();
14361436
};
1437-
exports.version = "19.2.0-www-modern-7ee75712-20250728";
1437+
exports.version = "19.2.0-www-modern-9be531cd-20250729";
14381438
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14391439
"function" ===
14401440
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,4 +610,4 @@ exports.useSyncExternalStore = function (
610610
exports.useTransition = function () {
611611
return ReactSharedInternals.H.useTransition();
612612
};
613-
exports.version = "19.2.0-www-classic-7ee75712-20250728";
613+
exports.version = "19.2.0-www-classic-9be531cd-20250729";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,4 +610,4 @@ exports.useSyncExternalStore = function (
610610
exports.useTransition = function () {
611611
return ReactSharedInternals.H.useTransition();
612612
};
613-
exports.version = "19.2.0-www-modern-7ee75712-20250728";
613+
exports.version = "19.2.0-www-modern-9be531cd-20250729";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ exports.useSyncExternalStore = function (
614614
exports.useTransition = function () {
615615
return ReactSharedInternals.H.useTransition();
616616
};
617-
exports.version = "19.2.0-www-classic-7ee75712-20250728";
617+
exports.version = "19.2.0-www-classic-9be531cd-20250729";
618618
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
619619
"function" ===
620620
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ exports.useSyncExternalStore = function (
614614
exports.useTransition = function () {
615615
return ReactSharedInternals.H.useTransition();
616616
};
617-
exports.version = "19.2.0-www-modern-7ee75712-20250728";
617+
exports.version = "19.2.0-www-modern-9be531cd-20250729";
618618
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
619619
"function" ===
620620
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,6 +3501,19 @@ __DEV__ &&
35013501
throw SuspenseException;
35023502
}
35033503
}
3504+
function resolveLazy(lazyType) {
3505+
try {
3506+
return callLazyInitInDEV(lazyType);
3507+
} catch (x) {
3508+
if (null !== x && "object" === typeof x && "function" === typeof x.then)
3509+
throw (
3510+
((suspendedThenable = x),
3511+
(needsToResetSuspendedThenableDEV = !0),
3512+
SuspenseException)
3513+
);
3514+
throw x;
3515+
}
3516+
}
35043517
function getSuspendedThenable() {
35053518
if (null === suspendedThenable)
35063519
throw Error(
@@ -3716,7 +3729,7 @@ __DEV__ &&
37163729
("object" === typeof elementType &&
37173730
null !== elementType &&
37183731
elementType.$$typeof === REACT_LAZY_TYPE &&
3719-
callLazyInitInDEV(elementType) === current.type))
3732+
resolveLazy(elementType) === current.type))
37203733
)
37213734
return (
37223735
(current = useFiber(current, element.props)),
@@ -3817,7 +3830,7 @@ __DEV__ &&
38173830
);
38183831
case REACT_LAZY_TYPE:
38193832
var _prevDebugInfo = pushDebugInfo(newChild._debugInfo);
3820-
newChild = callLazyInitInDEV(newChild);
3833+
newChild = resolveLazy(newChild);
38213834
returnFiber = createChild(returnFiber, newChild, lanes);
38223835
currentDebugInfo = _prevDebugInfo;
38233836
return returnFiber;
@@ -3893,7 +3906,7 @@ __DEV__ &&
38933906
case REACT_LAZY_TYPE:
38943907
return (
38953908
(key = pushDebugInfo(newChild._debugInfo)),
3896-
(newChild = callLazyInitInDEV(newChild)),
3909+
(newChild = resolveLazy(newChild)),
38973910
(returnFiber = updateSlot(
38983911
returnFiber,
38993912
oldFiber,
@@ -3987,7 +4000,7 @@ __DEV__ &&
39874000
);
39884001
case REACT_LAZY_TYPE:
39894002
var _prevDebugInfo7 = pushDebugInfo(newChild._debugInfo);
3990-
newChild = callLazyInitInDEV(newChild);
4003+
newChild = resolveLazy(newChild);
39914004
returnFiber = updateFromMap(
39924005
existingChildren,
39934006
returnFiber,
@@ -4065,7 +4078,7 @@ __DEV__ &&
40654078
});
40664079
break;
40674080
case REACT_LAZY_TYPE:
4068-
(child = callLazyInitInDEV(child)),
4081+
(child = resolveLazy(child)),
40694082
warnOnInvalidKey(returnFiber, workInProgress, child, knownKeys);
40704083
}
40714084
return knownKeys;
@@ -4336,7 +4349,7 @@ __DEV__ &&
43364349
("object" === typeof key &&
43374350
null !== key &&
43384351
key.$$typeof === REACT_LAZY_TYPE &&
4339-
callLazyInitInDEV(key) === currentFirstChild.type)
4352+
resolveLazy(key) === currentFirstChild.type)
43404353
) {
43414354
deleteRemainingChildren(
43424355
returnFiber,
@@ -4428,7 +4441,7 @@ __DEV__ &&
44284441
case REACT_LAZY_TYPE:
44294442
return (
44304443
(prevDebugInfo = pushDebugInfo(newChild._debugInfo)),
4431-
(newChild = callLazyInitInDEV(newChild)),
4444+
(newChild = resolveLazy(newChild)),
44324445
(returnFiber = reconcileChildFibersImpl(
44334446
returnFiber,
44344447
currentFirstChild,
@@ -9116,7 +9129,7 @@ __DEV__ &&
91169129
case 16:
91179130
a: if (
91189131
((returnFiber = workInProgress.pendingProps),
9119-
(current = callLazyInitInDEV(workInProgress.elementType)),
9132+
(current = resolveLazy(workInProgress.elementType)),
91209133
(workInProgress.type = current),
91219134
"function" === typeof current)
91229135
)
@@ -17631,25 +17644,7 @@ __DEV__ &&
1763117644
pendingUNSAFE_ComponentWillUpdateWarnings = [];
1763217645
pendingLegacyContextWarning = new Map();
1763317646
};
17634-
var SuspenseException = Error(
17635-
"Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`."
17636-
),
17637-
SuspenseyCommitException = Error(
17638-
"Suspense Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React."
17639-
),
17640-
SuspenseActionException = Error(
17641-
"Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary."
17642-
),
17643-
noopSuspenseyCommitThenable = {
17644-
then: function () {
17645-
console.error(
17646-
'Internal React error: A listener was unexpectedly attached to a "noop" thenable. This is a bug in React. Please file an issue.'
17647-
);
17648-
}
17649-
},
17650-
suspendedThenable = null,
17651-
needsToResetSuspendedThenableDEV = !1,
17652-
callComponent = {
17647+
var callComponent = {
1765317648
react_stack_bottom_frame: function (Component, props, secondArg) {
1765417649
var wasRendering = isRendering;
1765517650
isRendering = !0;
@@ -17766,6 +17761,24 @@ __DEV__ &&
1776617761
},
1776717762
callLazyInitInDEV =
1776817763
callLazyInit.react_stack_bottom_frame.bind(callLazyInit),
17764+
SuspenseException = Error(
17765+
"Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`."
17766+
),
17767+
SuspenseyCommitException = Error(
17768+
"Suspense Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React."
17769+
),
17770+
SuspenseActionException = Error(
17771+
"Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary."
17772+
),
17773+
noopSuspenseyCommitThenable = {
17774+
then: function () {
17775+
console.error(
17776+
'Internal React error: A listener was unexpectedly attached to a "noop" thenable. This is a bug in React. Please file an issue.'
17777+
);
17778+
}
17779+
},
17780+
suspendedThenable = null,
17781+
needsToResetSuspendedThenableDEV = !1,
1776917782
thenableState$1 = null,
1777017783
thenableIndexCounter$1 = 0,
1777117784
currentDebugInfo = null,
@@ -19305,10 +19318,10 @@ __DEV__ &&
1930519318
(function () {
1930619319
var internals = {
1930719320
bundleType: 1,
19308-
version: "19.2.0-www-classic-7ee75712-20250728",
19321+
version: "19.2.0-www-classic-9be531cd-20250729",
1930919322
rendererPackageName: "react-art",
1931019323
currentDispatcherRef: ReactSharedInternals,
19311-
reconcilerVersion: "19.2.0-www-classic-7ee75712-20250728"
19324+
reconcilerVersion: "19.2.0-www-classic-9be531cd-20250729"
1931219325
};
1931319326
internals.overrideHookState = overrideHookState;
1931419327
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19342,7 +19355,7 @@ __DEV__ &&
1934219355
exports.Shape = Shape;
1934319356
exports.Surface = Surface;
1934419357
exports.Text = Text;
19345-
exports.version = "19.2.0-www-classic-7ee75712-20250728";
19358+
exports.version = "19.2.0-www-classic-9be531cd-20250729";
1934619359
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1934719360
"function" ===
1934819361
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3407,6 +3407,19 @@ __DEV__ &&
34073407
throw SuspenseException;
34083408
}
34093409
}
3410+
function resolveLazy(lazyType) {
3411+
try {
3412+
return callLazyInitInDEV(lazyType);
3413+
} catch (x) {
3414+
if (null !== x && "object" === typeof x && "function" === typeof x.then)
3415+
throw (
3416+
((suspendedThenable = x),
3417+
(needsToResetSuspendedThenableDEV = !0),
3418+
SuspenseException)
3419+
);
3420+
throw x;
3421+
}
3422+
}
34103423
function getSuspendedThenable() {
34113424
if (null === suspendedThenable)
34123425
throw Error(
@@ -3622,7 +3635,7 @@ __DEV__ &&
36223635
("object" === typeof elementType &&
36233636
null !== elementType &&
36243637
elementType.$$typeof === REACT_LAZY_TYPE &&
3625-
callLazyInitInDEV(elementType) === current.type))
3638+
resolveLazy(elementType) === current.type))
36263639
)
36273640
return (
36283641
(current = useFiber(current, element.props)),
@@ -3723,7 +3736,7 @@ __DEV__ &&
37233736
);
37243737
case REACT_LAZY_TYPE:
37253738
var _prevDebugInfo = pushDebugInfo(newChild._debugInfo);
3726-
newChild = callLazyInitInDEV(newChild);
3739+
newChild = resolveLazy(newChild);
37273740
returnFiber = createChild(returnFiber, newChild, lanes);
37283741
currentDebugInfo = _prevDebugInfo;
37293742
return returnFiber;
@@ -3799,7 +3812,7 @@ __DEV__ &&
37993812
case REACT_LAZY_TYPE:
38003813
return (
38013814
(key = pushDebugInfo(newChild._debugInfo)),
3802-
(newChild = callLazyInitInDEV(newChild)),
3815+
(newChild = resolveLazy(newChild)),
38033816
(returnFiber = updateSlot(
38043817
returnFiber,
38053818
oldFiber,
@@ -3893,7 +3906,7 @@ __DEV__ &&
38933906
);
38943907
case REACT_LAZY_TYPE:
38953908
var _prevDebugInfo7 = pushDebugInfo(newChild._debugInfo);
3896-
newChild = callLazyInitInDEV(newChild);
3909+
newChild = resolveLazy(newChild);
38973910
returnFiber = updateFromMap(
38983911
existingChildren,
38993912
returnFiber,
@@ -3971,7 +3984,7 @@ __DEV__ &&
39713984
});
39723985
break;
39733986
case REACT_LAZY_TYPE:
3974-
(child = callLazyInitInDEV(child)),
3987+
(child = resolveLazy(child)),
39753988
warnOnInvalidKey(returnFiber, workInProgress, child, knownKeys);
39763989
}
39773990
return knownKeys;
@@ -4242,7 +4255,7 @@ __DEV__ &&
42424255
("object" === typeof key &&
42434256
null !== key &&
42444257
key.$$typeof === REACT_LAZY_TYPE &&
4245-
callLazyInitInDEV(key) === currentFirstChild.type)
4258+
resolveLazy(key) === currentFirstChild.type)
42464259
) {
42474260
deleteRemainingChildren(
42484261
returnFiber,
@@ -4334,7 +4347,7 @@ __DEV__ &&
43344347
case REACT_LAZY_TYPE:
43354348
return (
43364349
(prevDebugInfo = pushDebugInfo(newChild._debugInfo)),
4337-
(newChild = callLazyInitInDEV(newChild)),
4350+
(newChild = resolveLazy(newChild)),
43384351
(returnFiber = reconcileChildFibersImpl(
43394352
returnFiber,
43404353
currentFirstChild,
@@ -8945,7 +8958,7 @@ __DEV__ &&
89458958
case 16:
89468959
a: if (
89478960
((returnFiber = workInProgress.pendingProps),
8948-
(current = callLazyInitInDEV(workInProgress.elementType)),
8961+
(current = resolveLazy(workInProgress.elementType)),
89498962
(workInProgress.type = current),
89508963
"function" === typeof current)
89518964
)
@@ -17403,25 +17416,7 @@ __DEV__ &&
1740317416
pendingUNSAFE_ComponentWillUpdateWarnings = [];
1740417417
pendingLegacyContextWarning = new Map();
1740517418
};
17406-
var SuspenseException = Error(
17407-
"Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`."
17408-
),
17409-
SuspenseyCommitException = Error(
17410-
"Suspense Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React."
17411-
),
17412-
SuspenseActionException = Error(
17413-
"Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary."
17414-
),
17415-
noopSuspenseyCommitThenable = {
17416-
then: function () {
17417-
console.error(
17418-
'Internal React error: A listener was unexpectedly attached to a "noop" thenable. This is a bug in React. Please file an issue.'
17419-
);
17420-
}
17421-
},
17422-
suspendedThenable = null,
17423-
needsToResetSuspendedThenableDEV = !1,
17424-
callComponent = {
17419+
var callComponent = {
1742517420
react_stack_bottom_frame: function (Component, props, secondArg) {
1742617421
var wasRendering = isRendering;
1742717422
isRendering = !0;
@@ -17538,6 +17533,24 @@ __DEV__ &&
1753817533
},
1753917534
callLazyInitInDEV =
1754017535
callLazyInit.react_stack_bottom_frame.bind(callLazyInit),
17536+
SuspenseException = Error(
17537+
"Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`."
17538+
),
17539+
SuspenseyCommitException = Error(
17540+
"Suspense Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React."
17541+
),
17542+
SuspenseActionException = Error(
17543+
"Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary."
17544+
),
17545+
noopSuspenseyCommitThenable = {
17546+
then: function () {
17547+
console.error(
17548+
'Internal React error: A listener was unexpectedly attached to a "noop" thenable. This is a bug in React. Please file an issue.'
17549+
);
17550+
}
17551+
},
17552+
suspendedThenable = null,
17553+
needsToResetSuspendedThenableDEV = !1,
1754117554
thenableState$1 = null,
1754217555
thenableIndexCounter$1 = 0,
1754317556
currentDebugInfo = null,
@@ -19076,10 +19089,10 @@ __DEV__ &&
1907619089
(function () {
1907719090
var internals = {
1907819091
bundleType: 1,
19079-
version: "19.2.0-www-modern-7ee75712-20250728",
19092+
version: "19.2.0-www-modern-9be531cd-20250729",
1908019093
rendererPackageName: "react-art",
1908119094
currentDispatcherRef: ReactSharedInternals,
19082-
reconcilerVersion: "19.2.0-www-modern-7ee75712-20250728"
19095+
reconcilerVersion: "19.2.0-www-modern-9be531cd-20250729"
1908319096
};
1908419097
internals.overrideHookState = overrideHookState;
1908519098
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19113,7 +19126,7 @@ __DEV__ &&
1911319126
exports.Shape = Shape;
1911419127
exports.Surface = Surface;
1911519128
exports.Text = Text;
19116-
exports.version = "19.2.0-www-modern-7ee75712-20250728";
19129+
exports.version = "19.2.0-www-modern-9be531cd-20250729";
1911719130
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1911819131
"function" ===
1911919132
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)