diff --git a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
index 5f1875f5f431c..786eaac4f4483 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
@@ -115,7 +115,9 @@ describe('ReactDOMServerLifecycles', () => {
}
}
- ReactDOMServer.renderToString();
+ expect(() => ReactDOMServer.renderToString()).toErrorDev(
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.',
+ );
});
it('should update instance.state with value returned from getDerivedStateFromProps', () => {
@@ -279,8 +281,8 @@ describe('ReactDOMServerLifecycles', () => {
}
}
- expect(() => ReactDOMServer.renderToString()).toWarnDev(
- 'componentWillMount has been renamed',
+ expect(() => ReactDOMServer.renderToString()).toErrorDev(
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.',
);
});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index c8ccbfc245a1d..752af3b2c7377 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -194,6 +194,7 @@ const didWarnAboutModulePatternComponent = {};
const didWarnAboutDeprecatedWillMount = {};
const didWarnAboutUndefinedDerivedState = {};
const didWarnAboutUninitializedState = {};
+const didWarnAboutLegacyLifecyclesAndDerivedState = {};
const valuePropNames = ['value', 'defaultValue'];
const newlineEatingTags = {
listing: true,
@@ -475,6 +476,79 @@ function resolve(
didWarnAboutUninitializedState[componentName] = true;
}
}
+
+ // If new component APIs are defined, "unsafe" lifecycles won't be called.
+ // Warn about these lifecycles if they are present.
+ // Don't warn about react-lifecycles-compat polyfilled methods though.
+ if (
+ typeof Component.getDerivedStateFromProps === 'function' ||
+ typeof inst.getSnapshotBeforeUpdate === 'function'
+ ) {
+ let foundWillMountName = null;
+ let foundWillReceivePropsName = null;
+ let foundWillUpdateName = null;
+ if (
+ typeof inst.componentWillMount === 'function' &&
+ inst.componentWillMount.__suppressDeprecationWarning !== true
+ ) {
+ foundWillMountName = 'componentWillMount';
+ } else if (typeof inst.UNSAFE_componentWillMount === 'function') {
+ foundWillMountName = 'UNSAFE_componentWillMount';
+ }
+ if (
+ typeof inst.componentWillReceiveProps === 'function' &&
+ inst.componentWillReceiveProps.__suppressDeprecationWarning !==
+ true
+ ) {
+ foundWillReceivePropsName = 'componentWillReceiveProps';
+ } else if (
+ typeof inst.UNSAFE_componentWillReceiveProps === 'function'
+ ) {
+ foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
+ }
+ if (
+ typeof inst.componentWillUpdate === 'function' &&
+ inst.componentWillUpdate.__suppressDeprecationWarning !== true
+ ) {
+ foundWillUpdateName = 'componentWillUpdate';
+ } else if (typeof inst.UNSAFE_componentWillUpdate === 'function') {
+ foundWillUpdateName = 'UNSAFE_componentWillUpdate';
+ }
+ if (
+ foundWillMountName !== null ||
+ foundWillReceivePropsName !== null ||
+ foundWillUpdateName !== null
+ ) {
+ const componentName =
+ getComponentNameFromType(Component) || 'Component';
+ const newApiName =
+ typeof Component.getDerivedStateFromProps === 'function'
+ ? 'getDerivedStateFromProps()'
+ : 'getSnapshotBeforeUpdate()';
+ if (!didWarnAboutLegacyLifecyclesAndDerivedState[componentName]) {
+ didWarnAboutLegacyLifecyclesAndDerivedState[
+ componentName
+ ] = true;
+ console.error(
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
+ '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://reactjs.org/link/unsafe-component-lifecycles',
+ componentName,
+ newApiName,
+ foundWillMountName !== null
+ ? `\n ${foundWillMountName}`
+ : '',
+ foundWillReceivePropsName !== null
+ ? `\n ${foundWillReceivePropsName}`
+ : '',
+ foundWillUpdateName !== null
+ ? `\n ${foundWillUpdateName}`
+ : '',
+ );
+ }
+ }
+ }
}
const partialState = Component.getDerivedStateFromProps.call(
@@ -575,32 +649,32 @@ function resolve(
typeof inst.componentWillMount === 'function'
) {
if (typeof inst.componentWillMount === 'function') {
- if (__DEV__) {
- if (
- warnAboutDeprecatedLifecycles &&
- inst.componentWillMount.__suppressDeprecationWarning !== true
- ) {
- const componentName =
- getComponentNameFromType(Component) || 'Unknown';
-
- if (!didWarnAboutDeprecatedWillMount[componentName]) {
- console.warn(
- // keep this warning in sync with ReactStrictModeWarning.js
- 'componentWillMount has been renamed, and is not recommended for use. ' +
- 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
- '* Move code from componentWillMount to componentDidMount (preferred in most cases) ' +
- 'or the constructor.\n' +
- '\nPlease update the following components: %s',
- componentName,
- );
- didWarnAboutDeprecatedWillMount[componentName] = true;
- }
- }
- }
-
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
if (typeof Component.getDerivedStateFromProps !== 'function') {
+ if (__DEV__) {
+ if (
+ warnAboutDeprecatedLifecycles &&
+ inst.componentWillMount.__suppressDeprecationWarning !== true
+ ) {
+ const componentName =
+ getComponentNameFromType(Component) || 'Unknown';
+
+ if (!didWarnAboutDeprecatedWillMount[componentName]) {
+ console.warn(
+ // keep this warning in sync with ReactStrictModeWarning.js
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
+ 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
+ '* Move code from componentWillMount to componentDidMount (preferred in most cases) ' +
+ 'or the constructor.\n' +
+ '\nPlease update the following components: %s',
+ componentName,
+ );
+ didWarnAboutDeprecatedWillMount[componentName] = true;
+ }
+ }
+ }
+
inst.componentWillMount();
}
}
diff --git a/packages/react-server/src/ReactFizzClassComponent.js b/packages/react-server/src/ReactFizzClassComponent.js
index b0cfc62c12ae2..2e085c3f1a711 100644
--- a/packages/react-server/src/ReactFizzClassComponent.js
+++ b/packages/react-server/src/ReactFizzClassComponent.js
@@ -10,13 +10,17 @@
import {emptyContextObject} from './ReactFizzContext';
import {readContext} from './ReactFizzNewContext';
-import {disableLegacyContext} from 'shared/ReactFeatureFlags';
+import {
+ disableLegacyContext,
+ warnAboutDeprecatedLifecycles,
+} from 'shared/ReactFeatureFlags';
import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import isArray from 'shared/isArray';
const didWarnAboutNoopUpdateForComponent = {};
+const didWarnAboutDeprecatedWillMount = {};
let didWarnAboutUninitializedState;
let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
@@ -531,6 +535,28 @@ function callComponentWillMount(type, instance) {
const oldState = instance.state;
if (typeof instance.componentWillMount === 'function') {
+ if (__DEV__) {
+ if (
+ warnAboutDeprecatedLifecycles &&
+ instance.componentWillMount.__suppressDeprecationWarning !== true
+ ) {
+ const componentName = getComponentNameFromType(type) || 'Unknown';
+
+ if (!didWarnAboutDeprecatedWillMount[componentName]) {
+ console.warn(
+ // keep this warning in sync with ReactStrictModeWarning.js
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
+ 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
+ '* Move code from componentWillMount to componentDidMount (preferred in most cases) ' +
+ 'or the constructor.\n' +
+ '\nPlease update the following components: %s',
+ componentName,
+ );
+ didWarnAboutDeprecatedWillMount[componentName] = true;
+ }
+ }
+ }
+
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index d7189ab06c976..927aef27f4a7f 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -1032,13 +1032,27 @@ function renderElement(
}
}
+ let info = '';
+ if (__DEV__) {
+ if (
+ type === undefined ||
+ (typeof type === 'object' &&
+ type !== null &&
+ Object.keys(type).length === 0)
+ ) {
+ info +=
+ ' You likely forgot to export your component from the file ' +
+ "it's defined in, or you might have mixed up default and " +
+ 'named imports.';
+ }
+ }
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
- '',
+ info,
);
}