diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js
index e90b02e441958..51126c6cdbcc4 100644
--- a/packages/react-reconciler/src/ReactFiberLane.js
+++ b/packages/react-reconciler/src/ReactFiberLane.js
@@ -636,6 +636,7 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
root.entangledLanes &= remainingLanes;
root.errorRecoveryDisabledLanes &= remainingLanes;
+ root.shellSuspendCounter = 0;
const entanglements = root.entanglements;
const expirationTimes = root.expirationTimes;
diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js
index 7b295bdbe80b8..e65e25b97df6b 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.js
@@ -74,6 +74,7 @@ function FiberRootNode(
this.expiredLanes = NoLanes;
this.finishedLanes = NoLanes;
this.errorRecoveryDisabledLanes = NoLanes;
+ this.shellSuspendCounter = 0;
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index f486bf2648dc3..92a18624cb42e 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -1944,6 +1944,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
markRenderStarted(lanes);
}
+ let didSuspendInShell = false;
outer: do {
try {
if (
@@ -1969,6 +1970,42 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
workInProgressRootExitStatus = RootDidNotComplete;
break outer;
}
+ case SuspendedOnImmediate:
+ case SuspendedOnData: {
+ if (!didSuspendInShell) {
+ const handler = getSuspenseHandler();
+ if (handler === null) {
+ didSuspendInShell = true;
+ const counter = ++root.shellSuspendCounter;
+ if (counter > 100) {
+ // This root has suspended repeatedly in the shell without
+ // making any progress (i.e. committing something). This is
+ // highly suggestive of an infinite ping loop, often caused by
+ // an accidental Async Client Component.
+ //
+ // During a transition, we can suspend the work loop until the
+ // promise to resolve, but this is a sync render, so that's
+ // not an option. We also can't show a fallback, because none
+ // was provided. So our last resort is to throw an error.
+ //
+ // TODO: Remove this error in a future release. Other ways
+ // of handling this case include forcing a concurrent render,
+ // or putting the whole root into offscreen mode.
+ const asyncClientComponentError = new 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.',
+ );
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(unitOfWork, asyncClientComponentError);
+ break;
+ }
+ }
+ }
+ // Intentional fallthrough
+ }
default: {
// Unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js
index eae140ac4ed73..50a0ec85f9f25 100644
--- a/packages/react-reconciler/src/ReactInternalTypes.js
+++ b/packages/react-reconciler/src/ReactInternalTypes.js
@@ -248,6 +248,7 @@ type BaseFiberRootProperties = {
pingedLanes: Lanes,
expiredLanes: Lanes,
errorRecoveryDisabledLanes: Lanes,
+ shellSuspendCounter: number,
finishedLanes: Lanes,
diff --git a/packages/react-reconciler/src/__tests__/ReactUse-test.js b/packages/react-reconciler/src/__tests__/ReactUse-test.js
index ce3add950be5f..b225528a3ff57 100644
--- a/packages/react-reconciler/src/__tests__/ReactUse-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactUse-test.js
@@ -17,6 +17,7 @@ let waitFor;
let waitForPaint;
let assertLog;
let waitForAll;
+let waitForMicrotasks;
describe('ReactUse', () => {
beforeEach(() => {
@@ -40,6 +41,7 @@ describe('ReactUse', () => {
assertLog = InternalTestUtils.assertLog;
waitForPaint = InternalTestUtils.waitForPaint;
waitFor = InternalTestUtils.waitFor;
+ waitForMicrotasks = InternalTestUtils.waitForMicrotasks;
pendingTextRequests = new Map();
});
@@ -1616,4 +1618,96 @@ describe('ReactUse', () => {
assertLog(['C']);
expect(root).toMatchRenderedOutput('C');
});
+
+ // @gate !forceConcurrentByDefaultForTesting
+ test('an async component outside of a Suspense boundary crashes with an error (resolves in microtask)', async () => {
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ if (this.state.error) {
+ return ;
+ }
+ return this.props.children;
+ }
+ }
+
+ async function AsyncClientComponent() {
+ return ;
+ }
+
+ const root = ReactNoop.createRoot();
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ assertLog([
+ '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.',
+ '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.',
+ ]);
+ expect(root).toMatchRenderedOutput(
+ '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.',
+ );
+ });
+
+
+ // @gate !forceConcurrentByDefaultForTesting
+ test('an async component outside of a Suspense boundary crashes with an error (resolves in macrotask)', async () => {
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ if (this.state.error) {
+ return ;
+ }
+ return this.props.children;
+ }
+ }
+
+ async function AsyncClientComponent() {
+ await waitForMicrotasks();
+ return ;
+ }
+
+ const root = ReactNoop.createRoot();
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ assertLog([
+ '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.',
+ '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.',
+ ]);
+ expect(root).toMatchRenderedOutput(
+ '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.',
+ );
+ });
});
diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
index a1197be405837..abee206668ebb 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -13,22 +13,22 @@
// Use __VARIANT__ to simulate a GK. The tests will be run twice: once
// with the __VARIANT__ set to `true`, and once set to `false`.
-export const disableInputAttributeSyncing = __VARIANT__;
-export const disableIEWorkarounds = __VARIANT__;
-export const enableLegacyFBSupport = __VARIANT__;
-export const enableUseRefAccessWarning = __VARIANT__;
-export const enableProfilerNestedUpdateScheduledHook = __VARIANT__;
-export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
-export const enableLazyContextPropagation = __VARIANT__;
-export const forceConcurrentByDefaultForTesting = __VARIANT__;
-export const enableUnifiedSyncLane = __VARIANT__;
-export const enableTransitionTracing = __VARIANT__;
-export const enableCustomElementPropertySupport = __VARIANT__;
-export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
-export const diffInCommitPhase = __VARIANT__;
-export const enableAsyncActions = __VARIANT__;
-export const alwaysThrottleRetries = __VARIANT__;
-export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
+export const disableInputAttributeSyncing = !__VARIANT__;
+export const disableIEWorkarounds = !__VARIANT__;
+export const enableLegacyFBSupport = !__VARIANT__;
+export const enableUseRefAccessWarning = !__VARIANT__;
+export const enableProfilerNestedUpdateScheduledHook = !__VARIANT__;
+export const disableSchedulerTimeoutInWorkLoop = !__VARIANT__;
+export const enableLazyContextPropagation = !__VARIANT__;
+export const forceConcurrentByDefaultForTesting = !__VARIANT__;
+export const enableUnifiedSyncLane = !__VARIANT__;
+export const enableTransitionTracing = !__VARIANT__;
+export const enableCustomElementPropertySupport = !__VARIANT__;
+export const enableDeferRootSchedulingToMicrotask = !__VARIANT__;
+export const diffInCommitPhase = !__VARIANT__;
+export const enableAsyncActions = !__VARIANT__;
+export const alwaysThrottleRetries = !__VARIANT__;
+export const enableDO_NOT_USE_disableStrictPassiveEffect = !__VARIANT__;
// Enable this flag to help with concurrent mode debugging.
// It logs information to the console about React scheduling, rendering, and commit phases.
@@ -36,7 +36,7 @@ export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
// NOTE: This feature will only work in DEV mode; all callsites are wrapped with __DEV__.
export const enableDebugTracing = __EXPERIMENTAL__;
-export const enableSchedulingProfiler = __VARIANT__;
+export const enableSchedulingProfiler = !__VARIANT__;
// These are already tested in both modes using the build type dimension,
// so we don't need to use __VARIANT__ to get extra coverage.
diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json
index b1ae802b33b56..3423de2fa99b4 100644
--- a/scripts/error-codes/codes.json
+++ b/scripts/error-codes/codes.json
@@ -466,5 +466,6 @@
"478": "Thenable should have already resolved. This is a bug in React.",
"479": "Cannot update optimistic state while rendering.",
"480": "File/Blob fields are not yet supported in progressive forms. It probably means you are closing over binary data or FormData in a Server Action.",
- "481": "Tried to encode a Server Action from a different instance than the encoder is from. This is a bug in React."
+ "481": "Tried to encode a Server Action from a different instance than the encoder is from. This is a bug in React.",
+ "482": "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."
}