Skip to content

Commit 2b3922a

Browse files
committed
Always warn if client component suspends with an uncached promise (#28159)
Previously we only warned during a synchronous update, because we eventually want to support async client components in controlled scenarios, like during navigations. However, we're going to warn in all cases for now until we figure out how that should work. DiffTrain build for commit 178f435.
1 parent 47a7b36 commit 2b3922a

13 files changed

+266
-218
lines changed

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

+60-44
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<202c1c7cbff4135eff9604c4cb02a215>>
10+
* @generated SignedSource<<dcb3a30556188c6961c11e3ffe2a0c3a>>
1111
*/
1212

1313
"use strict";
@@ -4770,7 +4770,14 @@ if (__DEV__) {
47704770
}
47714771
}
47724772

4773-
var ReactCurrentActQueue$2 = ReactSharedInternals.ReactCurrentActQueue; // An error that is thrown (e.g. by `use`) to trigger Suspense. If we
4773+
var ReactCurrentActQueue$2 = ReactSharedInternals.ReactCurrentActQueue;
4774+
4775+
function getThenablesFromState(state) {
4776+
{
4777+
var devState = state;
4778+
return devState.thenables;
4779+
}
4780+
} // An error that is thrown (e.g. by `use`) to trigger Suspense. If we
47744781
// detect this is caught by userspace, we'll log a warning in development.
47754782

47764783
var SuspenseException = new Error(
@@ -4803,7 +4810,12 @@ if (__DEV__) {
48034810
function createThenableState() {
48044811
// The ThenableState is created the first time a component suspends. If it
48054812
// suspends again, we'll reuse the same state.
4806-
return [];
4813+
{
4814+
return {
4815+
didWarnAboutUncachedPromise: false,
4816+
thenables: []
4817+
};
4818+
}
48074819
}
48084820
function isThenableResolved(thenable) {
48094821
var status = thenable.status;
@@ -4817,16 +4829,45 @@ if (__DEV__) {
48174829
ReactCurrentActQueue$2.didUsePromise = true;
48184830
}
48194831

4820-
var previous = thenableState[index];
4832+
var trackedThenables = getThenablesFromState(thenableState);
4833+
var previous = trackedThenables[index];
48214834

48224835
if (previous === undefined) {
4823-
thenableState.push(thenable);
4836+
trackedThenables.push(thenable);
48244837
} else {
48254838
if (previous !== thenable) {
48264839
// Reuse the previous thenable, and drop the new one. We can assume
48274840
// they represent the same value, because components are idempotent.
4828-
// Avoid an unhandled rejection errors for the Promises that we'll
4841+
{
4842+
var thenableStateDev = thenableState;
4843+
4844+
if (!thenableStateDev.didWarnAboutUncachedPromise) {
4845+
// We should only warn the first time an uncached thenable is
4846+
// discovered per component, because if there are multiple, the
4847+
// subsequent ones are likely derived from the first.
4848+
//
4849+
// We track this on the thenableState instead of deduping using the
4850+
// component name like we usually do, because in the case of a
4851+
// promise-as-React-node, the owner component is likely different from
4852+
// the parent that's currently being reconciled. We'd have to track
4853+
// the owner using state, which we're trying to move away from. Though
4854+
// since this is dev-only, maybe that'd be OK.
4855+
//
4856+
// However, another benefit of doing it this way is we might
4857+
// eventually have a thenableState per memo/Forget boundary instead
4858+
// of per component, so this would allow us to have more
4859+
// granular warnings.
4860+
thenableStateDev.didWarnAboutUncachedPromise = true; // TODO: This warning should link to a corresponding docs page.
4861+
4862+
error(
4863+
"A component was suspended by an uncached promise. Creating " +
4864+
"promises inside a Client Component or hook is not yet " +
4865+
"supported, except via a Suspense-compatible library or framework."
4866+
);
4867+
}
4868+
} // Avoid an unhandled rejection errors for the Promises that we'll
48294869
// intentionally ignore.
4870+
48304871
thenable.then(noop, noop);
48314872
thenable = previous;
48324873
}
@@ -6903,7 +6944,7 @@ if (__DEV__) {
69036944
}
69046945
}
69056946

6906-
function warnIfAsyncClientComponent(Component, componentDoesIncludeHooks) {
6947+
function warnIfAsyncClientComponent(Component) {
69076948
{
69086949
// This dev-only check only works for detecting native async functions,
69096950
// not transpiled ones. There's also a prod check that we use to prevent
@@ -6915,43 +6956,20 @@ if (__DEV__) {
69156956
"[object AsyncFunction]";
69166957

69176958
if (isAsyncFunction) {
6918-
// Encountered an async Client Component. This is not yet supported,
6919-
// except in certain constrained cases, like during a route navigation.
6959+
// Encountered an async Client Component. This is not yet supported.
69206960
var componentName = getComponentNameFromFiber(
69216961
currentlyRenderingFiber$1
69226962
);
69236963

69246964
if (!didWarnAboutAsyncClientComponent.has(componentName)) {
6925-
didWarnAboutAsyncClientComponent.add(componentName); // Check if this is a sync update. We use the "root" render lanes here
6926-
// because the "subtree" render lanes may include additional entangled
6927-
// lanes related to revealing previously hidden content.
6965+
didWarnAboutAsyncClientComponent.add(componentName);
69286966

6929-
var root = getWorkInProgressRoot();
6930-
var rootRenderLanes = getWorkInProgressRootRenderLanes();
6931-
6932-
if (root !== null && includesBlockingLane(root, rootRenderLanes)) {
6933-
error(
6934-
"async/await is not yet supported in Client Components, only " +
6935-
"Server Components. This error is often caused by accidentally " +
6936-
"adding `'use client'` to a module that was originally written " +
6937-
"for the server."
6938-
);
6939-
} else {
6940-
// This is a concurrent (Transition, Retry, etc) render. We don't
6941-
// warn in these cases.
6942-
//
6943-
// However, Async Components are forbidden to include hooks, even
6944-
// during a transition, so let's check for that here.
6945-
//
6946-
// TODO: Add a corresponding warning to Server Components runtime.
6947-
if (componentDoesIncludeHooks) {
6948-
error(
6949-
"Hooks are not supported inside an async component. This " +
6950-
"error is often caused by accidentally adding `'use client'` " +
6951-
"to a module that was originally written for the server."
6952-
);
6953-
}
6954-
}
6967+
error(
6968+
"async/await is not yet supported in Client Components, only " +
6969+
"Server Components. This error is often caused by accidentally " +
6970+
"adding `'use client'` to a module that was originally written " +
6971+
"for the server."
6972+
);
69556973
}
69566974
}
69576975
}
@@ -7034,6 +7052,7 @@ if (__DEV__) {
70347052

70357053
ignorePreviousDependencies =
70367054
current !== null && current.type !== workInProgress.type;
7055+
warnIfAsyncClientComponent(Component);
70377056
}
70387057

70397058
workInProgress.memoizedState = null;
@@ -7109,16 +7128,13 @@ if (__DEV__) {
71097128
);
71107129
}
71117130

7112-
finishRenderingHooks(current, workInProgress, Component);
7131+
finishRenderingHooks(current, workInProgress);
71137132
return children;
71147133
}
71157134

71167135
function finishRenderingHooks(current, workInProgress, Component) {
71177136
{
71187137
workInProgress._debugHookTypes = hookTypesDev;
7119-
var componentDoesIncludeHooks =
7120-
workInProgressHook !== null || thenableIndexCounter !== 0;
7121-
warnIfAsyncClientComponent(Component, componentDoesIncludeHooks);
71227138
} // We can assume the previous dispatcher is always this one, since we set it
71237139
// at the beginning of the render phase and there's no re-entrance.
71247140

@@ -7220,7 +7236,7 @@ if (__DEV__) {
72207236
props,
72217237
secondArg
72227238
);
7223-
finishRenderingHooks(current, workInProgress, Component);
7239+
finishRenderingHooks(current, workInProgress);
72247240
return children;
72257241
}
72267242

@@ -25607,7 +25623,7 @@ if (__DEV__) {
2560725623
return root;
2560825624
}
2560925625

25610-
var ReactVersion = "18.3.0-canary-554fc49f4-20240130";
25626+
var ReactVersion = "18.3.0-canary-178f43519-20240130";
2561125627

2561225628
// Might add PROFILE later.
2561325629

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

+16-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<876c7c325bf8c60091b5193da65519e1>>
10+
* @generated SignedSource<<8c4ed2fabb96724eadedb0d542cd9ecf>>
1111
*/
1212

1313
"use strict";
@@ -9144,19 +9144,19 @@ function wrapFiber(fiber) {
91449144
fiberToWrapper.set(fiber, wrapper));
91459145
return wrapper;
91469146
}
9147-
var devToolsConfig$jscomp$inline_1029 = {
9147+
var devToolsConfig$jscomp$inline_1031 = {
91489148
findFiberByHostInstance: function () {
91499149
throw Error("TestRenderer does not support findFiberByHostInstance()");
91509150
},
91519151
bundleType: 0,
9152-
version: "18.3.0-canary-554fc49f4-20240130",
9152+
version: "18.3.0-canary-178f43519-20240130",
91539153
rendererPackageName: "react-test-renderer"
91549154
};
9155-
var internals$jscomp$inline_1205 = {
9156-
bundleType: devToolsConfig$jscomp$inline_1029.bundleType,
9157-
version: devToolsConfig$jscomp$inline_1029.version,
9158-
rendererPackageName: devToolsConfig$jscomp$inline_1029.rendererPackageName,
9159-
rendererConfig: devToolsConfig$jscomp$inline_1029.rendererConfig,
9155+
var internals$jscomp$inline_1207 = {
9156+
bundleType: devToolsConfig$jscomp$inline_1031.bundleType,
9157+
version: devToolsConfig$jscomp$inline_1031.version,
9158+
rendererPackageName: devToolsConfig$jscomp$inline_1031.rendererPackageName,
9159+
rendererConfig: devToolsConfig$jscomp$inline_1031.rendererConfig,
91609160
overrideHookState: null,
91619161
overrideHookStateDeletePath: null,
91629162
overrideHookStateRenamePath: null,
@@ -9173,26 +9173,26 @@ var internals$jscomp$inline_1205 = {
91739173
return null === fiber ? null : fiber.stateNode;
91749174
},
91759175
findFiberByHostInstance:
9176-
devToolsConfig$jscomp$inline_1029.findFiberByHostInstance ||
9176+
devToolsConfig$jscomp$inline_1031.findFiberByHostInstance ||
91779177
emptyFindFiberByHostInstance,
91789178
findHostInstancesForRefresh: null,
91799179
scheduleRefresh: null,
91809180
scheduleRoot: null,
91819181
setRefreshHandler: null,
91829182
getCurrentFiber: null,
9183-
reconcilerVersion: "18.3.0-canary-554fc49f4-20240130"
9183+
reconcilerVersion: "18.3.0-canary-178f43519-20240130"
91849184
};
91859185
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
9186-
var hook$jscomp$inline_1206 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
9186+
var hook$jscomp$inline_1208 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
91879187
if (
9188-
!hook$jscomp$inline_1206.isDisabled &&
9189-
hook$jscomp$inline_1206.supportsFiber
9188+
!hook$jscomp$inline_1208.isDisabled &&
9189+
hook$jscomp$inline_1208.supportsFiber
91909190
)
91919191
try {
9192-
(rendererID = hook$jscomp$inline_1206.inject(
9193-
internals$jscomp$inline_1205
9192+
(rendererID = hook$jscomp$inline_1208.inject(
9193+
internals$jscomp$inline_1207
91949194
)),
9195-
(injectedHook = hook$jscomp$inline_1206);
9195+
(injectedHook = hook$jscomp$inline_1208);
91969196
} catch (err) {}
91979197
}
91989198
exports._Scheduler = Scheduler;

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

+16-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<409ac0ee00a9c6d06fcdff021f44a0bf>>
10+
* @generated SignedSource<<c27ac45a247951b8b8199e9df960e4cf>>
1111
*/
1212

1313
"use strict";
@@ -9572,19 +9572,19 @@ function wrapFiber(fiber) {
95729572
fiberToWrapper.set(fiber, wrapper));
95739573
return wrapper;
95749574
}
9575-
var devToolsConfig$jscomp$inline_1071 = {
9575+
var devToolsConfig$jscomp$inline_1073 = {
95769576
findFiberByHostInstance: function () {
95779577
throw Error("TestRenderer does not support findFiberByHostInstance()");
95789578
},
95799579
bundleType: 0,
9580-
version: "18.3.0-canary-554fc49f4-20240130",
9580+
version: "18.3.0-canary-178f43519-20240130",
95819581
rendererPackageName: "react-test-renderer"
95829582
};
9583-
var internals$jscomp$inline_1246 = {
9584-
bundleType: devToolsConfig$jscomp$inline_1071.bundleType,
9585-
version: devToolsConfig$jscomp$inline_1071.version,
9586-
rendererPackageName: devToolsConfig$jscomp$inline_1071.rendererPackageName,
9587-
rendererConfig: devToolsConfig$jscomp$inline_1071.rendererConfig,
9583+
var internals$jscomp$inline_1248 = {
9584+
bundleType: devToolsConfig$jscomp$inline_1073.bundleType,
9585+
version: devToolsConfig$jscomp$inline_1073.version,
9586+
rendererPackageName: devToolsConfig$jscomp$inline_1073.rendererPackageName,
9587+
rendererConfig: devToolsConfig$jscomp$inline_1073.rendererConfig,
95889588
overrideHookState: null,
95899589
overrideHookStateDeletePath: null,
95909590
overrideHookStateRenamePath: null,
@@ -9601,26 +9601,26 @@ var internals$jscomp$inline_1246 = {
96019601
return null === fiber ? null : fiber.stateNode;
96029602
},
96039603
findFiberByHostInstance:
9604-
devToolsConfig$jscomp$inline_1071.findFiberByHostInstance ||
9604+
devToolsConfig$jscomp$inline_1073.findFiberByHostInstance ||
96059605
emptyFindFiberByHostInstance,
96069606
findHostInstancesForRefresh: null,
96079607
scheduleRefresh: null,
96089608
scheduleRoot: null,
96099609
setRefreshHandler: null,
96109610
getCurrentFiber: null,
9611-
reconcilerVersion: "18.3.0-canary-554fc49f4-20240130"
9611+
reconcilerVersion: "18.3.0-canary-178f43519-20240130"
96129612
};
96139613
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
9614-
var hook$jscomp$inline_1247 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
9614+
var hook$jscomp$inline_1249 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
96159615
if (
9616-
!hook$jscomp$inline_1247.isDisabled &&
9617-
hook$jscomp$inline_1247.supportsFiber
9616+
!hook$jscomp$inline_1249.isDisabled &&
9617+
hook$jscomp$inline_1249.supportsFiber
96189618
)
96199619
try {
9620-
(rendererID = hook$jscomp$inline_1247.inject(
9621-
internals$jscomp$inline_1246
9620+
(rendererID = hook$jscomp$inline_1249.inject(
9621+
internals$jscomp$inline_1248
96229622
)),
9623-
(injectedHook = hook$jscomp$inline_1247);
9623+
(injectedHook = hook$jscomp$inline_1249);
96249624
} catch (err) {}
96259625
}
96269626
exports._Scheduler = Scheduler;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if (__DEV__) {
2424
) {
2525
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2626
}
27-
var ReactVersion = "18.3.0-canary-554fc49f4-20240130";
27+
var ReactVersion = "18.3.0-canary-178f43519-20240130";
2828

2929
// ATTENTION
3030
// When adding new symbols to this file,

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -545,4 +545,4 @@ exports.useSyncExternalStore = function (
545545
exports.useTransition = function () {
546546
return ReactCurrentDispatcher.current.useTransition();
547547
};
548-
exports.version = "18.3.0-canary-554fc49f4-20240130";
548+
exports.version = "18.3.0-canary-178f43519-20240130";

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ exports.useSyncExternalStore = function (
541541
exports.useTransition = function () {
542542
return ReactCurrentDispatcher.current.useTransition();
543543
};
544-
exports.version = "18.3.0-canary-554fc49f4-20240130";
544+
exports.version = "18.3.0-canary-178f43519-20240130";
545545
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
546546
"function" ===
547547
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
554fc49f41465d914b15dc8eb2ec094f37824f7e
1+
178f4351947a842ff0b56700e9115b25ae8f20d0

0 commit comments

Comments
 (0)