Skip to content

Commit

Permalink
Always warn if client component suspends with an uncached promise (#2…
Browse files Browse the repository at this point in the history
…8159)

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.
  • Loading branch information
acdlite committed Jan 30, 2024
1 parent 47a7b36 commit 2b3922a
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<202c1c7cbff4135eff9604c4cb02a215>>
* @generated SignedSource<<dcb3a30556188c6961c11e3ffe2a0c3a>>
*/

"use strict";
Expand Down Expand Up @@ -4770,7 +4770,14 @@ if (__DEV__) {
}
}

var ReactCurrentActQueue$2 = ReactSharedInternals.ReactCurrentActQueue; // An error that is thrown (e.g. by `use`) to trigger Suspense. If we
var ReactCurrentActQueue$2 = ReactSharedInternals.ReactCurrentActQueue;

function getThenablesFromState(state) {
{
var devState = state;
return devState.thenables;
}
} // An error that is thrown (e.g. by `use`) to trigger Suspense. If we
// detect this is caught by userspace, we'll log a warning in development.

var SuspenseException = new Error(
Expand Down Expand Up @@ -4803,7 +4810,12 @@ if (__DEV__) {
function createThenableState() {
// The ThenableState is created the first time a component suspends. If it
// suspends again, we'll reuse the same state.
return [];
{
return {
didWarnAboutUncachedPromise: false,
thenables: []
};
}
}
function isThenableResolved(thenable) {
var status = thenable.status;
Expand All @@ -4817,16 +4829,45 @@ if (__DEV__) {
ReactCurrentActQueue$2.didUsePromise = true;
}

var previous = thenableState[index];
var trackedThenables = getThenablesFromState(thenableState);
var previous = trackedThenables[index];

if (previous === undefined) {
thenableState.push(thenable);
trackedThenables.push(thenable);
} else {
if (previous !== thenable) {
// Reuse the previous thenable, and drop the new one. We can assume
// they represent the same value, because components are idempotent.
// Avoid an unhandled rejection errors for the Promises that we'll
{
var thenableStateDev = thenableState;

if (!thenableStateDev.didWarnAboutUncachedPromise) {
// We should only warn the first time an uncached thenable is
// discovered per component, because if there are multiple, the
// subsequent ones are likely derived from the first.
//
// We track this on the thenableState instead of deduping using the
// component name like we usually do, because in the case of a
// promise-as-React-node, the owner component is likely different from
// the parent that's currently being reconciled. We'd have to track
// the owner using state, which we're trying to move away from. Though
// since this is dev-only, maybe that'd be OK.
//
// However, another benefit of doing it this way is we might
// eventually have a thenableState per memo/Forget boundary instead
// of per component, so this would allow us to have more
// granular warnings.
thenableStateDev.didWarnAboutUncachedPromise = true; // TODO: This warning should link to a corresponding docs page.

error(
"A component was suspended by an uncached promise. Creating " +
"promises inside a Client Component or hook is not yet " +
"supported, except via a Suspense-compatible library or framework."
);
}
} // Avoid an unhandled rejection errors for the Promises that we'll
// intentionally ignore.

thenable.then(noop, noop);
thenable = previous;
}
Expand Down Expand Up @@ -6903,7 +6944,7 @@ if (__DEV__) {
}
}

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

if (isAsyncFunction) {
// Encountered an async Client Component. This is not yet supported,
// except in certain constrained cases, like during a route navigation.
// Encountered an async Client Component. This is not yet supported.
var componentName = getComponentNameFromFiber(
currentlyRenderingFiber$1
);

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

var root = getWorkInProgressRoot();
var rootRenderLanes = getWorkInProgressRootRenderLanes();

if (root !== null && includesBlockingLane(root, rootRenderLanes)) {
error(
"async/await is not yet supported in Client Components, only " +
"Server Components. This error is often caused by accidentally " +
"adding `'use client'` to a module that was originally written " +
"for the server."
);
} else {
// This is a concurrent (Transition, Retry, etc) render. We don't
// warn in these cases.
//
// However, Async Components are forbidden to include hooks, even
// during a transition, so let's check for that here.
//
// TODO: Add a corresponding warning to Server Components runtime.
if (componentDoesIncludeHooks) {
error(
"Hooks are not supported inside an async component. This " +
"error is often caused by accidentally adding `'use client'` " +
"to a module that was originally written for the server."
);
}
}
error(
"async/await is not yet supported in Client Components, only " +
"Server Components. This error is often caused by accidentally " +
"adding `'use client'` to a module that was originally written " +
"for the server."
);
}
}
}
Expand Down Expand Up @@ -7034,6 +7052,7 @@ if (__DEV__) {

ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
warnIfAsyncClientComponent(Component);
}

workInProgress.memoizedState = null;
Expand Down Expand Up @@ -7109,16 +7128,13 @@ if (__DEV__) {
);
}

finishRenderingHooks(current, workInProgress, Component);
finishRenderingHooks(current, workInProgress);
return children;
}

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

Expand Down Expand Up @@ -7220,7 +7236,7 @@ if (__DEV__) {
props,
secondArg
);
finishRenderingHooks(current, workInProgress, Component);
finishRenderingHooks(current, workInProgress);
return children;
}

Expand Down Expand Up @@ -25607,7 +25623,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "18.3.0-canary-554fc49f4-20240130";
var ReactVersion = "18.3.0-canary-178f43519-20240130";

// Might add PROFILE later.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<876c7c325bf8c60091b5193da65519e1>>
* @generated SignedSource<<8c4ed2fabb96724eadedb0d542cd9ecf>>
*/

"use strict";
Expand Down Expand Up @@ -9144,19 +9144,19 @@ function wrapFiber(fiber) {
fiberToWrapper.set(fiber, wrapper));
return wrapper;
}
var devToolsConfig$jscomp$inline_1029 = {
var devToolsConfig$jscomp$inline_1031 = {
findFiberByHostInstance: function () {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-canary-554fc49f4-20240130",
version: "18.3.0-canary-178f43519-20240130",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1205 = {
bundleType: devToolsConfig$jscomp$inline_1029.bundleType,
version: devToolsConfig$jscomp$inline_1029.version,
rendererPackageName: devToolsConfig$jscomp$inline_1029.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1029.rendererConfig,
var internals$jscomp$inline_1207 = {
bundleType: devToolsConfig$jscomp$inline_1031.bundleType,
version: devToolsConfig$jscomp$inline_1031.version,
rendererPackageName: devToolsConfig$jscomp$inline_1031.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1031.rendererConfig,
overrideHookState: null,
overrideHookStateDeletePath: null,
overrideHookStateRenamePath: null,
Expand All @@ -9173,26 +9173,26 @@ var internals$jscomp$inline_1205 = {
return null === fiber ? null : fiber.stateNode;
},
findFiberByHostInstance:
devToolsConfig$jscomp$inline_1029.findFiberByHostInstance ||
devToolsConfig$jscomp$inline_1031.findFiberByHostInstance ||
emptyFindFiberByHostInstance,
findHostInstancesForRefresh: null,
scheduleRefresh: null,
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-canary-554fc49f4-20240130"
reconcilerVersion: "18.3.0-canary-178f43519-20240130"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1206 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_1208 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_1206.isDisabled &&
hook$jscomp$inline_1206.supportsFiber
!hook$jscomp$inline_1208.isDisabled &&
hook$jscomp$inline_1208.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_1206.inject(
internals$jscomp$inline_1205
(rendererID = hook$jscomp$inline_1208.inject(
internals$jscomp$inline_1207
)),
(injectedHook = hook$jscomp$inline_1206);
(injectedHook = hook$jscomp$inline_1208);
} catch (err) {}
}
exports._Scheduler = Scheduler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<409ac0ee00a9c6d06fcdff021f44a0bf>>
* @generated SignedSource<<c27ac45a247951b8b8199e9df960e4cf>>
*/

"use strict";
Expand Down Expand Up @@ -9572,19 +9572,19 @@ function wrapFiber(fiber) {
fiberToWrapper.set(fiber, wrapper));
return wrapper;
}
var devToolsConfig$jscomp$inline_1071 = {
var devToolsConfig$jscomp$inline_1073 = {
findFiberByHostInstance: function () {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-canary-554fc49f4-20240130",
version: "18.3.0-canary-178f43519-20240130",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1246 = {
bundleType: devToolsConfig$jscomp$inline_1071.bundleType,
version: devToolsConfig$jscomp$inline_1071.version,
rendererPackageName: devToolsConfig$jscomp$inline_1071.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1071.rendererConfig,
var internals$jscomp$inline_1248 = {
bundleType: devToolsConfig$jscomp$inline_1073.bundleType,
version: devToolsConfig$jscomp$inline_1073.version,
rendererPackageName: devToolsConfig$jscomp$inline_1073.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1073.rendererConfig,
overrideHookState: null,
overrideHookStateDeletePath: null,
overrideHookStateRenamePath: null,
Expand All @@ -9601,26 +9601,26 @@ var internals$jscomp$inline_1246 = {
return null === fiber ? null : fiber.stateNode;
},
findFiberByHostInstance:
devToolsConfig$jscomp$inline_1071.findFiberByHostInstance ||
devToolsConfig$jscomp$inline_1073.findFiberByHostInstance ||
emptyFindFiberByHostInstance,
findHostInstancesForRefresh: null,
scheduleRefresh: null,
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-canary-554fc49f4-20240130"
reconcilerVersion: "18.3.0-canary-178f43519-20240130"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1247 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_1249 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_1247.isDisabled &&
hook$jscomp$inline_1247.supportsFiber
!hook$jscomp$inline_1249.isDisabled &&
hook$jscomp$inline_1249.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_1247.inject(
internals$jscomp$inline_1246
(rendererID = hook$jscomp$inline_1249.inject(
internals$jscomp$inline_1248
)),
(injectedHook = hook$jscomp$inline_1247);
(injectedHook = hook$jscomp$inline_1249);
} catch (err) {}
}
exports._Scheduler = Scheduler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (__DEV__) {
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = "18.3.0-canary-554fc49f4-20240130";
var ReactVersion = "18.3.0-canary-178f43519-20240130";

// ATTENTION
// When adding new symbols to this file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-canary-554fc49f4-20240130";
exports.version = "18.3.0-canary-178f43519-20240130";
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-canary-554fc49f4-20240130";
exports.version = "18.3.0-canary-178f43519-20240130";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
554fc49f41465d914b15dc8eb2ec094f37824f7e
178f4351947a842ff0b56700e9115b25ae8f20d0
Loading

0 comments on commit 2b3922a

Please sign in to comment.