@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969 return self;
7070}
7171
72- var ReactVersion = "18.3.0-www-classic-9da53968 ";
72+ var ReactVersion = "18.3.0-www-classic-4bc5632c ";
7373
7474var LegacyRoot = 0;
7575var ConcurrentRoot = 1;
@@ -3013,7 +3013,14 @@ function describeNativeComponentFrame(fn, construct) {
30133013 // tries to access React/ReactDOM/props. We should probably make this throw
30143014 // in simple components too
30153015
3016- fn();
3016+ var maybePromise = fn(); // If the function component returns a promise, it's likely an async
3017+ // component, which we don't yet support. Attach a noop catch handler to
3018+ // silence the error.
3019+ // TODO: Implement component stacks for async client components?
3020+
3021+ if (maybePromise && typeof maybePromise.catch === "function") {
3022+ maybePromise.catch(function () {});
3023+ }
30173024 }
30183025 } catch (sample) {
30193026 // This is inlined manually because closure doesn't do it for us.
@@ -5401,6 +5408,7 @@ function trackUsedThenable(thenableState, thenable, index) {
54015408
54025409 case "rejected": {
54035410 var rejectedError = thenable.reason;
5411+ checkIfUseWrappedInAsyncCatch(rejectedError);
54045412 throw rejectedError;
54055413 }
54065414
@@ -5456,18 +5464,20 @@ function trackUsedThenable(thenableState, thenable, index) {
54565464 rejectedThenable.reason = error;
54575465 }
54585466 }
5459- );
5460- } // Check one more time in case the thenable resolved synchronously.
5467+ ); // Check one more time in case the thenable resolved synchronously.
54615468
5462- switch (thenable.status) {
5463- case "fulfilled": {
5464- var fulfilledThenable = thenable;
5465- return fulfilledThenable.value;
5466- }
5469+ switch (thenable.status) {
5470+ case "fulfilled": {
5471+ var fulfilledThenable = thenable;
5472+ return fulfilledThenable.value;
5473+ }
54675474
5468- case "rejected": {
5469- var rejectedThenable = thenable;
5470- throw rejectedThenable.reason;
5475+ case "rejected": {
5476+ var rejectedThenable = thenable;
5477+ var _rejectedError = rejectedThenable.reason;
5478+ checkIfUseWrappedInAsyncCatch(_rejectedError);
5479+ throw _rejectedError;
5480+ }
54715481 }
54725482 } // Suspend.
54735483 //
@@ -5526,6 +5536,22 @@ function checkIfUseWrappedInTryCatch() {
55265536
55275537 return false;
55285538}
5539+ function checkIfUseWrappedInAsyncCatch(rejectedReason) {
5540+ // This check runs in prod, too, because it prevents a more confusing
5541+ // downstream error, where SuspenseException is caught by a promise and
5542+ // thrown asynchronously.
5543+ // TODO: Another way to prevent SuspenseException from leaking into an async
5544+ // execution context is to check the dispatcher every time `use` is called,
5545+ // or some equivalent. That might be preferable for other reasons, too, since
5546+ // it matches how we prevent similar mistakes for other hooks.
5547+ if (rejectedReason === SuspenseException) {
5548+ throw new Error(
5549+ "Hooks are not supported inside an async component. This " +
5550+ "error is often caused by accidentally adding `'use client'` " +
5551+ "to a module that was originally written for the server."
5552+ );
5553+ }
5554+ }
55295555
55305556var thenableState$1 = null;
55315557var thenableIndexCounter$1 = 0;
@@ -7843,10 +7869,12 @@ var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher,
78437869var didWarnAboutMismatchedHooksForComponent;
78447870var didWarnUncachedGetSnapshot;
78457871var didWarnAboutUseWrappedInTryCatch;
7872+ var didWarnAboutAsyncClientComponent;
78467873
78477874{
78487875 didWarnAboutMismatchedHooksForComponent = new Set();
78497876 didWarnAboutUseWrappedInTryCatch = new Set();
7877+ didWarnAboutAsyncClientComponent = new Set();
78507878} // The effect "instance" is a shared object that remains the same for the entire
78517879// lifetime of an effect. In Rust terms, a RefCell. We use it to store the
78527880// "destroy" function that is returned from an effect, because that is stateful.
@@ -7987,6 +8015,57 @@ function warnOnHookMismatchInDev(currentHookName) {
79878015 }
79888016}
79898017
8018+ function warnIfAsyncClientComponent(Component, componentDoesIncludeHooks) {
8019+ {
8020+ // This dev-only check only works for detecting native async functions,
8021+ // not transpiled ones. There's also a prod check that we use to prevent
8022+ // async client components from crashing the app; the prod one works even
8023+ // for transpiled async functions. Neither mechanism is completely
8024+ // bulletproof but together they cover the most common cases.
8025+ var isAsyncFunction = // $FlowIgnore[method-unbinding]
8026+ Object.prototype.toString.call(Component) === "[object AsyncFunction]";
8027+
8028+ if (isAsyncFunction) {
8029+ // Encountered an async Client Component. This is not yet supported,
8030+ // except in certain constrained cases, like during a route navigation.
8031+ var componentName = getComponentNameFromFiber(currentlyRenderingFiber$1);
8032+
8033+ if (!didWarnAboutAsyncClientComponent.has(componentName)) {
8034+ didWarnAboutAsyncClientComponent.add(componentName); // Check if this is a sync update. We use the "root" render lanes here
8035+ // because the "subtree" render lanes may include additional entangled
8036+ // lanes related to revealing previously hidden content.
8037+
8038+ var root = getWorkInProgressRoot();
8039+ var rootRenderLanes = getWorkInProgressRootRenderLanes();
8040+
8041+ if (root !== null && includesBlockingLane(root, rootRenderLanes)) {
8042+ error(
8043+ "async/await is not yet supported in Client Components, only " +
8044+ "Server Components. This error is often caused by accidentally " +
8045+ "adding `'use client'` to a module that was originally written " +
8046+ "for the server."
8047+ );
8048+ } else {
8049+ // This is a concurrent (Transition, Retry, etc) render. We don't
8050+ // warn in these cases.
8051+ //
8052+ // However, Async Components are forbidden to include hooks, even
8053+ // during a transition, so let's check for that here.
8054+ //
8055+ // TODO: Add a corresponding warning to Server Components runtime.
8056+ if (componentDoesIncludeHooks) {
8057+ error(
8058+ "Hooks are not supported inside an async component. This " +
8059+ "error is often caused by accidentally adding `'use client'` " +
8060+ "to a module that was originally written for the server."
8061+ );
8062+ }
8063+ }
8064+ }
8065+ }
8066+ }
8067+ }
8068+
79908069function throwInvalidHookError() {
79918070 throw new Error(
79928071 "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for" +
@@ -8156,18 +8235,20 @@ function renderWithHooks(
81568235 }
81578236 }
81588237
8159- finishRenderingHooks(current, workInProgress);
8238+ finishRenderingHooks(current, workInProgress, Component );
81608239 return children;
81618240}
81628241
8163- function finishRenderingHooks(current, workInProgress) {
8164- // We can assume the previous dispatcher is always this one, since we set it
8165- // at the beginning of the render phase and there's no re-entrance.
8166- ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
8167-
8242+ function finishRenderingHooks(current, workInProgress, Component) {
81688243 {
81698244 workInProgress._debugHookTypes = hookTypesDev;
8170- } // This check uses currentHook so that it works the same in DEV and prod bundles.
8245+ var componentDoesIncludeHooks =
8246+ workInProgressHook !== null || thenableIndexCounter !== 0;
8247+ warnIfAsyncClientComponent(Component, componentDoesIncludeHooks);
8248+ } // We can assume the previous dispatcher is always this one, since we set it
8249+ // at the beginning of the render phase and there's no re-entrance.
8250+
8251+ ReactCurrentDispatcher$1.current = ContextOnlyDispatcher; // This check uses currentHook so that it works the same in DEV and prod bundles.
81718252 // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
81728253
81738254 var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
@@ -8240,7 +8321,12 @@ function finishRenderingHooks(current, workInProgress) {
82408321 var componentName =
82418322 getComponentNameFromFiber(workInProgress) || "Unknown";
82428323
8243- if (!didWarnAboutUseWrappedInTryCatch.has(componentName)) {
8324+ if (
8325+ !didWarnAboutUseWrappedInTryCatch.has(componentName) && // This warning also fires if you suspend with `use` inside an
8326+ // async component. Since we warn for that above, we'll silence this
8327+ // second warning by checking here.
8328+ !didWarnAboutAsyncClientComponent.has(componentName)
8329+ ) {
82448330 didWarnAboutUseWrappedInTryCatch.add(componentName);
82458331
82468332 error(
@@ -8280,7 +8366,7 @@ function replaySuspendedComponentWithHooks(
82808366 props,
82818367 secondArg
82828368 );
8283- finishRenderingHooks(current, workInProgress);
8369+ finishRenderingHooks(current, workInProgress, Component );
82848370 return children;
82858371}
82868372
0 commit comments