Skip to content

Commit 9f66192

Browse files
committed
Fix: useTransition after use gets stuck in pending state (#29670)
When a component suspends with `use`, we switch to the "re-render" dispatcher during the subsequent render attempt, so that we can reuse the work from the initial attempt. However, once we run out of hooks from the previous attempt, we should switch back to the regular "update" dispatcher. This is conceptually the same fix as the one introduced in #26232. That fix only accounted for initial mount, but the useTransition regression test added in f829733 illustrates that we need to handle updates, too. The issue affects more than just useTransition but because most of the behavior between the "re-render" and "update" dispatchers is the same it's hard to contrive other scenarios in a test, which is probably why it took so long for someone to notice. Closes #28923 and #29209 --------- Co-authored-by: eps1lon <sebastian.silbermann@vercel.com> DiffTrain build for commit adbec0c.
1 parent 6ce16b6 commit 9f66192

File tree

13 files changed

+182
-80
lines changed

13 files changed

+182
-80
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-test-renderer/cjs/ReactTestRenderer-dev.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<75b6042ec2df1ed24f9d3d5c89c9b994>>
10+
* @generated SignedSource<<843340fb623449714d5807ec34c388a7>>
1111
*/
1212

1313
'use strict';
@@ -7843,15 +7843,39 @@ function useThenable(thenable) {
78437843
thenableState = createThenableState();
78447844
}
78457845

7846-
var result = trackUsedThenable(thenableState, thenable, index);
7846+
var result = trackUsedThenable(thenableState, thenable, index); // When something suspends with `use`, we replay the component with the
7847+
// "re-render" dispatcher instead of the "mount" or "update" dispatcher.
7848+
//
7849+
// But if there are additional hooks that occur after the `use` invocation
7850+
// that suspended, they wouldn't have been processed during the previous
7851+
// attempt. So after we invoke `use` again, we may need to switch from the
7852+
// "re-render" dispatcher back to the "mount" or "update" dispatcher. That's
7853+
// what the following logic accounts for.
7854+
//
7855+
// TODO: Theoretically this logic only needs to go into the rerender
7856+
// dispatcher. Could optimize, but probably not be worth it.
7857+
// This is the same logic as in updateWorkInProgressHook.
7858+
7859+
var workInProgressFiber = currentlyRenderingFiber$1;
7860+
var nextWorkInProgressHook = workInProgressHook === null ? // We're at the beginning of the list, so read from the first hook from
7861+
// the fiber.
7862+
workInProgressFiber.memoizedState : workInProgressHook.next;
7863+
7864+
if (nextWorkInProgressHook !== null) ; else {
7865+
// There are no remaining hooks from the previous attempt. We're no longer
7866+
// in "re-render" mode. Switch to the normal mount or update dispatcher.
7867+
//
7868+
// This is the same as the logic in renderWithHooks, except we don't bother
7869+
// to track the hook types debug information in this case (sufficient to
7870+
// only do that when nothing suspends).
7871+
var currentFiber = workInProgressFiber.alternate;
78477872

7848-
if (currentlyRenderingFiber$1.alternate === null && (workInProgressHook === null ? currentlyRenderingFiber$1.memoizedState === null : workInProgressHook.next === null)) {
7849-
// Initial render, and either this is the first time the component is
7850-
// called, or there were no Hooks called after this use() the previous
7851-
// time (perhaps because it threw). Subsequent Hook calls should use the
7852-
// mount dispatcher.
78537873
{
7854-
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
7874+
if (currentFiber !== null && currentFiber.memoizedState !== null) {
7875+
ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
7876+
} else {
7877+
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
7878+
}
78557879
}
78567880
}
78577881

@@ -23471,7 +23495,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2347123495
return root;
2347223496
}
2347323497

23474-
var ReactVersion = '19.0.0-rc-5c420e3824-20240531';
23498+
var ReactVersion = '19.0.0-rc-adbec0c25a-20240531';
2347523499

2347623500
/*
2347723501
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-test-renderer/cjs/ReactTestRenderer-prod.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<0c59fb69281d1d909e6de56cf4430600>>
10+
* @generated SignedSource<<905cc175856264e313ce8da1a2a4238b>>
1111
*/
1212

1313
"use strict";
@@ -2478,11 +2478,16 @@ function useThenable(thenable) {
24782478
thenableIndexCounter += 1;
24792479
null === thenableState && (thenableState = []);
24802480
thenable = trackUsedThenable(thenableState, thenable, index);
2481-
null === currentlyRenderingFiber$1.alternate &&
2481+
index = currentlyRenderingFiber$1;
2482+
null ===
24822483
(null === workInProgressHook
2483-
? null === currentlyRenderingFiber$1.memoizedState
2484-
: null === workInProgressHook.next) &&
2485-
(ReactSharedInternals.H = HooksDispatcherOnMount);
2484+
? index.memoizedState
2485+
: workInProgressHook.next) &&
2486+
((index = index.alternate),
2487+
(ReactSharedInternals.H =
2488+
null === index || null === index.memoizedState
2489+
? HooksDispatcherOnMount
2490+
: HooksDispatcherOnUpdate));
24862491
return thenable;
24872492
}
24882493
function use(usable) {
@@ -9298,7 +9303,7 @@ var devToolsConfig$jscomp$inline_1047 = {
92989303
throw Error("TestRenderer does not support findFiberByHostInstance()");
92999304
},
93009305
bundleType: 0,
9301-
version: "19.0.0-rc-5c420e3824-20240531",
9306+
version: "19.0.0-rc-adbec0c25a-20240531",
93029307
rendererPackageName: "react-test-renderer"
93039308
};
93049309
var internals$jscomp$inline_1234 = {
@@ -9329,7 +9334,7 @@ var internals$jscomp$inline_1234 = {
93299334
scheduleRoot: null,
93309335
setRefreshHandler: null,
93319336
getCurrentFiber: null,
9332-
reconcilerVersion: "19.0.0-rc-5c420e3824-20240531"
9337+
reconcilerVersion: "19.0.0-rc-adbec0c25a-20240531"
93339338
};
93349339
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
93359340
var hook$jscomp$inline_1235 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-test-renderer/cjs/ReactTestRenderer-profiling.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<0a4e2053c331cd297df75b0c0a284142>>
10+
* @generated SignedSource<<2ec503c3ec9cc5ba080608bcfdeb069e>>
1111
*/
1212

1313
"use strict";
@@ -2566,11 +2566,16 @@ function useThenable(thenable) {
25662566
thenableIndexCounter += 1;
25672567
null === thenableState && (thenableState = []);
25682568
thenable = trackUsedThenable(thenableState, thenable, index);
2569-
null === currentlyRenderingFiber$1.alternate &&
2569+
index = currentlyRenderingFiber$1;
2570+
null ===
25702571
(null === workInProgressHook
2571-
? null === currentlyRenderingFiber$1.memoizedState
2572-
: null === workInProgressHook.next) &&
2573-
(ReactSharedInternals.H = HooksDispatcherOnMount);
2572+
? index.memoizedState
2573+
: workInProgressHook.next) &&
2574+
((index = index.alternate),
2575+
(ReactSharedInternals.H =
2576+
null === index || null === index.memoizedState
2577+
? HooksDispatcherOnMount
2578+
: HooksDispatcherOnUpdate));
25742579
return thenable;
25752580
}
25762581
function use(usable) {
@@ -9920,7 +9925,7 @@ var devToolsConfig$jscomp$inline_1130 = {
99209925
throw Error("TestRenderer does not support findFiberByHostInstance()");
99219926
},
99229927
bundleType: 0,
9923-
version: "19.0.0-rc-5c420e3824-20240531",
9928+
version: "19.0.0-rc-adbec0c25a-20240531",
99249929
rendererPackageName: "react-test-renderer"
99259930
};
99269931
(function (internals) {
@@ -9964,7 +9969,7 @@ var devToolsConfig$jscomp$inline_1130 = {
99649969
scheduleRoot: null,
99659970
setRefreshHandler: null,
99669971
getCurrentFiber: null,
9967-
reconcilerVersion: "19.0.0-rc-5c420e3824-20240531"
9972+
reconcilerVersion: "19.0.0-rc-adbec0c25a-20240531"
99689973
});
99699974
exports._Scheduler = Scheduler;
99709975
exports.act = act;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react/cjs/React-dev.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<7e91f8a3315accc44974ba6d3012b60f>>
10+
* @generated SignedSource<<4f7e50456e26afbb6944fc28181daa80>>
1111
*/
1212

1313
'use strict';
@@ -24,7 +24,7 @@ if (
2424
}
2525
var dynamicFlagsUntyped = require('ReactNativeInternalFeatureFlags');
2626

27-
var ReactVersion = '19.0.0-rc-5c420e3824-20240531';
27+
var ReactVersion = '19.0.0-rc-adbec0c25a-20240531';
2828

2929
// Re-export dynamic flags from the internal module.
3030
var dynamicFlags = dynamicFlagsUntyped; // We destructure each value before re-exporting to avoid a dynamic look-up on

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react/cjs/React-prod.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<2a9cffe28b099cbbf68478af1f393ff1>>
10+
* @generated SignedSource<<ab9c75532395cf1647c807f37789a8e4>>
1111
*/
1212

1313
"use strict";
@@ -604,4 +604,4 @@ exports.useSyncExternalStore = function (
604604
exports.useTransition = function () {
605605
return ReactSharedInternals.H.useTransition();
606606
};
607-
exports.version = "19.0.0-rc-5c420e3824-20240531";
607+
exports.version = "19.0.0-rc-adbec0c25a-20240531";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react/cjs/React-profiling.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<8ec2a6bbcfd86d30090ddd12c98add78>>
10+
* @generated SignedSource<<9f5c8cb9b979f6b6f6c25611a0886ae3>>
1111
*/
1212

1313
"use strict";
@@ -608,7 +608,7 @@ exports.useSyncExternalStore = function (
608608
exports.useTransition = function () {
609609
return ReactSharedInternals.H.useTransition();
610610
};
611-
exports.version = "19.0.0-rc-5c420e3824-20240531";
611+
exports.version = "19.0.0-rc-adbec0c25a-20240531";
612612
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
613613
"function" ===
614614
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5c420e3824859b33321b4bc9ce3119806fac56c2
1+
adbec0c25aff07f04b0678679554505ba2813168

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-dev.fb.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<9b439e19fdffa2fa6e386a55c7f2dc88>>
10+
* @generated SignedSource<<959db6b7a430088709ef79d2031321cd>>
1111
*/
1212

1313
'use strict';
@@ -10583,15 +10583,39 @@ function useThenable(thenable) {
1058310583
thenableState = createThenableState();
1058410584
}
1058510585

10586-
var result = trackUsedThenable(thenableState, thenable, index);
10586+
var result = trackUsedThenable(thenableState, thenable, index); // When something suspends with `use`, we replay the component with the
10587+
// "re-render" dispatcher instead of the "mount" or "update" dispatcher.
10588+
//
10589+
// But if there are additional hooks that occur after the `use` invocation
10590+
// that suspended, they wouldn't have been processed during the previous
10591+
// attempt. So after we invoke `use` again, we may need to switch from the
10592+
// "re-render" dispatcher back to the "mount" or "update" dispatcher. That's
10593+
// what the following logic accounts for.
10594+
//
10595+
// TODO: Theoretically this logic only needs to go into the rerender
10596+
// dispatcher. Could optimize, but probably not be worth it.
10597+
// This is the same logic as in updateWorkInProgressHook.
10598+
10599+
var workInProgressFiber = currentlyRenderingFiber$1;
10600+
var nextWorkInProgressHook = workInProgressHook === null ? // We're at the beginning of the list, so read from the first hook from
10601+
// the fiber.
10602+
workInProgressFiber.memoizedState : workInProgressHook.next;
10603+
10604+
if (nextWorkInProgressHook !== null) ; else {
10605+
// There are no remaining hooks from the previous attempt. We're no longer
10606+
// in "re-render" mode. Switch to the normal mount or update dispatcher.
10607+
//
10608+
// This is the same as the logic in renderWithHooks, except we don't bother
10609+
// to track the hook types debug information in this case (sufficient to
10610+
// only do that when nothing suspends).
10611+
var currentFiber = workInProgressFiber.alternate;
1058710612

10588-
if (currentlyRenderingFiber$1.alternate === null && (workInProgressHook === null ? currentlyRenderingFiber$1.memoizedState === null : workInProgressHook.next === null)) {
10589-
// Initial render, and either this is the first time the component is
10590-
// called, or there were no Hooks called after this use() the previous
10591-
// time (perhaps because it threw). Subsequent Hook calls should use the
10592-
// mount dispatcher.
1059310613
{
10594-
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
10614+
if (currentFiber !== null && currentFiber.memoizedState !== null) {
10615+
ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
10616+
} else {
10617+
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
10618+
}
1059510619
}
1059610620
}
1059710621

@@ -26206,7 +26230,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2620626230
return root;
2620726231
}
2620826232

26209-
var ReactVersion = '19.0.0-rc-5c420e3824-20240531';
26233+
var ReactVersion = '19.0.0-rc-adbec0c25a-20240531';
2621026234

2621126235
/*
2621226236
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-prod.fb.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<183c26ead26dbc699a54a97554f63097>>
10+
* @generated SignedSource<<d4d79b9c46da5483403dab31cf882201>>
1111
*/
1212

1313
"use strict";
@@ -3857,11 +3857,16 @@ function useThenable(thenable) {
38573857
thenableIndexCounter += 1;
38583858
null === thenableState && (thenableState = []);
38593859
thenable = trackUsedThenable(thenableState, thenable, index);
3860-
null === currentlyRenderingFiber$1.alternate &&
3860+
index = currentlyRenderingFiber$1;
3861+
null ===
38613862
(null === workInProgressHook
3862-
? null === currentlyRenderingFiber$1.memoizedState
3863-
: null === workInProgressHook.next) &&
3864-
(ReactSharedInternals.H = HooksDispatcherOnMount);
3863+
? index.memoizedState
3864+
: workInProgressHook.next) &&
3865+
((index = index.alternate),
3866+
(ReactSharedInternals.H =
3867+
null === index || null === index.memoizedState
3868+
? HooksDispatcherOnMount
3869+
: HooksDispatcherOnUpdate));
38653870
return thenable;
38663871
}
38673872
function use(usable) {
@@ -10551,7 +10556,7 @@ var roots = new Map(),
1055110556
devToolsConfig$jscomp$inline_1124 = {
1055210557
findFiberByHostInstance: getInstanceFromNode,
1055310558
bundleType: 0,
10554-
version: "19.0.0-rc-5c420e3824-20240531",
10559+
version: "19.0.0-rc-adbec0c25a-20240531",
1055510560
rendererPackageName: "react-native-renderer",
1055610561
rendererConfig: {
1055710562
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -10594,7 +10599,7 @@ var internals$jscomp$inline_1350 = {
1059410599
scheduleRoot: null,
1059510600
setRefreshHandler: null,
1059610601
getCurrentFiber: null,
10597-
reconcilerVersion: "19.0.0-rc-5c420e3824-20240531"
10602+
reconcilerVersion: "19.0.0-rc-adbec0c25a-20240531"
1059810603
};
1059910604
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1060010605
var hook$jscomp$inline_1351 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-profiling.fb.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<ddb3340d112903f5a5cd915605b6d398>>
10+
* @generated SignedSource<<8ccb12c6c371309a0a428e1772ab27d6>>
1111
*/
1212

1313
"use strict";
@@ -3979,11 +3979,16 @@ function useThenable(thenable) {
39793979
thenableIndexCounter += 1;
39803980
null === thenableState && (thenableState = []);
39813981
thenable = trackUsedThenable(thenableState, thenable, index);
3982-
null === currentlyRenderingFiber$1.alternate &&
3982+
index = currentlyRenderingFiber$1;
3983+
null ===
39833984
(null === workInProgressHook
3984-
? null === currentlyRenderingFiber$1.memoizedState
3985-
: null === workInProgressHook.next) &&
3986-
(ReactSharedInternals.H = HooksDispatcherOnMount);
3985+
? index.memoizedState
3986+
: workInProgressHook.next) &&
3987+
((index = index.alternate),
3988+
(ReactSharedInternals.H =
3989+
null === index || null === index.memoizedState
3990+
? HooksDispatcherOnMount
3991+
: HooksDispatcherOnUpdate));
39873992
return thenable;
39883993
}
39893994
function use(usable) {
@@ -11257,7 +11262,7 @@ var roots = new Map(),
1125711262
devToolsConfig$jscomp$inline_1205 = {
1125811263
findFiberByHostInstance: getInstanceFromNode,
1125911264
bundleType: 0,
11260-
version: "19.0.0-rc-5c420e3824-20240531",
11265+
version: "19.0.0-rc-adbec0c25a-20240531",
1126111266
rendererPackageName: "react-native-renderer",
1126211267
rendererConfig: {
1126311268
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -11313,7 +11318,7 @@ var roots = new Map(),
1131311318
scheduleRoot: null,
1131411319
setRefreshHandler: null,
1131511320
getCurrentFiber: null,
11316-
reconcilerVersion: "19.0.0-rc-5c420e3824-20240531"
11321+
reconcilerVersion: "19.0.0-rc-adbec0c25a-20240531"
1131711322
});
1131811323
exports.createPortal = function (children, containerTag) {
1131911324
return createPortal$1(

0 commit comments

Comments
 (0)