diff --git a/packages/react-reconciler/src/ReactDebugAsyncWarnings.js b/packages/react-reconciler/src/ReactDebugAsyncWarnings.js
index 5aeb102d69718..0dbd802df95af 100644
--- a/packages/react-reconciler/src/ReactDebugAsyncWarnings.js
+++ b/packages/react-reconciler/src/ReactDebugAsyncWarnings.js
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
import getComponentName from 'shared/getComponentName';
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
-import {AsyncUpdates} from './ReactTypeOfInternalContext';
+import {AsyncUpdates, PreAsyncUpdates} from './ReactTypeOfInternalContext';
import warning from 'fbjs/lib/warning';
type LIFECYCLE =
@@ -94,7 +94,10 @@ if (__DEV__) {
let maybeAsyncRoot = null;
while (fiber !== null) {
- if (fiber.internalContextTag & AsyncUpdates) {
+ if (
+ fiber.internalContextTag & AsyncUpdates ||
+ fiber.internalContextTag & PreAsyncUpdates
+ ) {
maybeAsyncRoot = fiber;
}
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 4eed41271806d..9a9bb5f978a66 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -26,7 +26,7 @@ import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
-import {AsyncUpdates} from './ReactTypeOfInternalContext';
+import {AsyncUpdates, PreAsyncUpdates} from './ReactTypeOfInternalContext';
import {
cacheContext,
getMaskedContext,
@@ -637,16 +637,24 @@ export default function(
if (
enableAsyncSubtreeAPI &&
workInProgress.type != null &&
- workInProgress.type.prototype != null &&
- workInProgress.type.prototype.unstable_isAsyncReactComponent === true
+ workInProgress.type.prototype != null
) {
- workInProgress.internalContextTag |= AsyncUpdates;
+ const prototype = workInProgress.type.prototype;
+
+ if (prototype.unstable_isAsyncReactComponent === true) {
+ workInProgress.internalContextTag |= AsyncUpdates;
+ } else if (prototype.unstable_isPreAsyncReactComponent === true) {
+ workInProgress.internalContextTag |= PreAsyncUpdates;
+ }
}
if (__DEV__) {
// If we're inside of an async sub-tree,
// Warn about any unsafe lifecycles on this class component.
- if (workInProgress.internalContextTag & AsyncUpdates) {
+ if (
+ workInProgress.internalContextTag & AsyncUpdates ||
+ workInProgress.internalContextTag & PreAsyncUpdates
+ ) {
ReactDebugAsyncWarnings.recordLifecycleWarnings(
workInProgress,
instance,
diff --git a/packages/react-reconciler/src/ReactTypeOfInternalContext.js b/packages/react-reconciler/src/ReactTypeOfInternalContext.js
index 24859845f79f0..015c90cca2d63 100644
--- a/packages/react-reconciler/src/ReactTypeOfInternalContext.js
+++ b/packages/react-reconciler/src/ReactTypeOfInternalContext.js
@@ -9,5 +9,6 @@
export type TypeOfInternalContext = number;
-export const NoContext = 0;
-export const AsyncUpdates = 1;
+export const NoContext = 0b00000000;
+export const AsyncUpdates = 0b00000001;
+export const PreAsyncUpdates = 0b00000010;
diff --git a/packages/react/src/React.js b/packages/react/src/React.js
index 61a59f83c530b..4a2081edbe86c 100644
--- a/packages/react/src/React.js
+++ b/packages/react/src/React.js
@@ -9,7 +9,12 @@ import assign from 'object-assign';
import ReactVersion from 'shared/ReactVersion';
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
-import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
+import {
+ Component,
+ PureComponent,
+ AsyncComponent,
+ PreAsyncComponent,
+} from './ReactBaseClasses';
import {forEach, map, count, toArray, only} from './ReactChildren';
import ReactCurrentOwner from './ReactCurrentOwner';
import {
@@ -37,6 +42,7 @@ const React = {
Component,
PureComponent,
unstable_AsyncComponent: AsyncComponent,
+ unstable_PreAsyncComponent: PreAsyncComponent,
Fragment: REACT_FRAGMENT_TYPE,
diff --git a/packages/react/src/ReactBaseClasses.js b/packages/react/src/ReactBaseClasses.js
index 13d4ab999bfba..b95d158041198 100644
--- a/packages/react/src/ReactBaseClasses.js
+++ b/packages/react/src/ReactBaseClasses.js
@@ -117,44 +117,56 @@ if (__DEV__) {
}
}
-/**
- * Base class helpers for the updating state of a component.
- */
+function ComponentDummy() {}
+ComponentDummy.prototype = Component.prototype;
+
+function configurePrototype(ComponentSubclass, prototypeProperties) {
+ const prototype = (ComponentSubclass.prototype = new ComponentDummy());
+ prototype.constructor = ComponentSubclass;
+
+ // Avoid an extra prototype jump for these methods.
+ Object.assign(prototype, Component.prototype);
+
+ // Mixin additional properties
+ Object.assign(prototype, prototypeProperties);
+}
+
+// Convenience component with default shallow equality check for sCU.
function PureComponent(props, context, updater) {
- // Duplicated from Component.
this.props = props;
this.context = context;
this.refs = emptyObject;
- // We initialize the default updater but the real one gets injected by the
- // renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
+configurePrototype(PureComponent, {isPureReactComponent: true});
-function ComponentDummy() {}
-ComponentDummy.prototype = Component.prototype;
-const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
-pureComponentPrototype.constructor = PureComponent;
-// Avoid an extra prototype jump for these methods.
-Object.assign(pureComponentPrototype, Component.prototype);
-pureComponentPrototype.isPureReactComponent = true;
-
+// Special component type that opts subtree into async rendering mode.
function AsyncComponent(props, context, updater) {
- // Duplicated from Component.
this.props = props;
this.context = context;
this.refs = emptyObject;
- // We initialize the default updater but the real one gets injected by the
- // renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
+configurePrototype(AsyncComponent, {
+ unstable_isAsyncReactComponent: true,
+ render: function render() {
+ return this.props.children;
+ },
+});
-const asyncComponentPrototype = (AsyncComponent.prototype = new ComponentDummy());
-asyncComponentPrototype.constructor = AsyncComponent;
-// Avoid an extra prototype jump for these methods.
-Object.assign(asyncComponentPrototype, Component.prototype);
-asyncComponentPrototype.unstable_isAsyncReactComponent = true;
-asyncComponentPrototype.render = function() {
- return this.props.children;
-};
+// Special component type that enables async rendering dev warnings.
+// This helps detect unsafe lifecycles without enabling actual async behavior.
+function PreAsyncComponent(props, context, updater) {
+ this.props = props;
+ this.context = context;
+ this.refs = emptyObject;
+ this.updater = updater || ReactNoopUpdateQueue;
+}
+configurePrototype(PreAsyncComponent, {
+ unstable_isPreAsyncReactComponent: true,
+ render: function render() {
+ return this.props.children;
+ },
+});
-export {Component, PureComponent, AsyncComponent};
+export {Component, PureComponent, AsyncComponent, PreAsyncComponent};
diff --git a/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js b/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js
index a559d401001ea..e8e6a51538133 100644
--- a/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js
+++ b/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js
@@ -419,5 +419,58 @@ describe('ReactAsyncClassComponent', () => {
expect(caughtError).not.toBe(null);
});
+
+ it('should also warn inside of pre-async trees', () => {
+ class SyncRoot extends React.Component {
+ UNSAFE_componentWillMount() {}
+ UNSAFE_componentWillUpdate() {}
+ UNSAFE_componentWillReceiveProps() {}
+ render() {
+ return