diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
index ef0ee8ac13b..0788c871157 100644
--- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
@@ -669,7 +669,7 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(, container)).toWarnDev(
- 'Defines both componentWillReceiveProps',
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.',
);
});
@@ -695,7 +695,93 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(, container)).toWarnDev(
- 'Defines both componentWillReceiveProps',
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.',
+ );
+ });
+
+ it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
+ const container = document.createElement('div');
+
+ class AllLegacyLifecycles extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {}
+ UNSAFE_componentWillReceiveProps() {}
+ componentWillUpdate() {}
+ render() {
+ return null;
+ }
+ }
+
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillMount\n' +
+ ' UNSAFE_componentWillReceiveProps\n' +
+ ' componentWillUpdate\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillMount extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ UNSAFE_componentWillMount() {}
+ render() {
+ return null;
+ }
+ }
+
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' UNSAFE_componentWillMount\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillMountAndUpdate extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {}
+ UNSAFE_componentWillUpdate() {}
+ render() {
+ return null;
+ }
+ }
+
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillMount\n' +
+ ' UNSAFE_componentWillUpdate\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillReceiveProps extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillReceiveProps() {}
+ render() {
+ return null;
+ }
+ }
+
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillReceiveProps\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
);
});
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 389dd4ca4fe..8cf9c376769 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -41,14 +41,14 @@ const isArray = Array.isArray;
let didWarnAboutStateAssignmentForComponent;
let didWarnAboutUndefinedDerivedState;
let didWarnAboutUninitializedState;
-let didWarnAboutWillReceivePropsAndDerivedState;
+let didWarnAboutLegacyLifecyclesAndDerivedState;
let warnOnInvalidCallback;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = {};
didWarnAboutUndefinedDerivedState = {};
didWarnAboutUninitializedState = {};
- didWarnAboutWillReceivePropsAndDerivedState = {};
+ didWarnAboutLegacyLifecyclesAndDerivedState = {};
const didWarnOnInvalidCallback = {};
@@ -419,20 +419,75 @@ export default function(
adoptClassInstance(workInProgress, instance);
if (__DEV__) {
- if (
- typeof ctor.getDerivedStateFromProps === 'function' &&
- state === null
- ) {
- const componentName = getComponentName(workInProgress) || 'Component';
- if (!didWarnAboutUninitializedState[componentName]) {
- warning(
- false,
- '%s: Did not properly initialize state during construction. ' +
- 'Expected state to be an object, but it was %s.',
- componentName,
- instance.state === null ? 'null' : 'undefined',
- );
- didWarnAboutUninitializedState[componentName] = true;
+ if (typeof ctor.getDerivedStateFromProps === 'function') {
+ if (state === null) {
+ const componentName = getComponentName(workInProgress) || 'Component';
+ if (!didWarnAboutUninitializedState[componentName]) {
+ warning(
+ false,
+ '%s: Did not properly initialize state during construction. ' +
+ 'Expected state to be an object, but it was %s.',
+ componentName,
+ instance.state === null ? 'null' : 'undefined',
+ );
+ didWarnAboutUninitializedState[componentName] = true;
+ }
+ }
+
+ // If getDerivedStateFromProps() is 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.
+ let foundWillMountName = null;
+ let foundWillReceivePropsName = null;
+ let foundWillUpdateName = null;
+ if (
+ typeof instance.componentWillMount === 'function' &&
+ instance.componentWillMount.__suppressDeprecationWarning !== true
+ ) {
+ foundWillMountName = 'componentWillMount';
+ } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
+ foundWillMountName = 'UNSAFE_componentWillMount';
+ }
+ if (
+ typeof instance.componentWillReceiveProps === 'function' &&
+ instance.componentWillReceiveProps.__suppressDeprecationWarning !==
+ true
+ ) {
+ foundWillReceivePropsName = 'componentWillReceiveProps';
+ } else if (
+ typeof instance.UNSAFE_componentWillReceiveProps === 'function'
+ ) {
+ foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
+ }
+ if (typeof instance.componentWillUpdate === 'function') {
+ foundWillUpdateName = 'componentWillUpdate';
+ } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
+ foundWillUpdateName = 'UNSAFE_componentWillUpdate';
+ }
+ if (
+ foundWillMountName !== null ||
+ foundWillReceivePropsName !== null ||
+ foundWillUpdateName !== null
+ ) {
+ const componentName = getComponentName(workInProgress) || 'Component';
+ if (!didWarnAboutLegacyLifecyclesAndDerivedState[componentName]) {
+ warning(
+ false,
+ 'Unsafe legacy lifecycles will not be called for components using ' +
+ 'the new getDerivedStateFromProps() API.\n\n' +
+ '%s uses getDerivedStateFromProps() 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://fb.me/react-async-component-lifecycle-hooks',
+ componentName,
+ foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
+ foundWillReceivePropsName !== null
+ ? `\n ${foundWillReceivePropsName}`
+ : '',
+ foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
+ );
+ didWarnAboutLegacyLifecyclesAndDerivedState[componentName] = true;
+ }
}
}
}
@@ -536,28 +591,6 @@ export default function(
const {type} = workInProgress;
if (typeof type.getDerivedStateFromProps === 'function') {
- if (__DEV__) {
- // Don't warn about react-lifecycles-compat polyfilled components
- if (
- (typeof instance.componentWillReceiveProps === 'function' &&
- instance.componentWillReceiveProps.__suppressDeprecationWarning !==
- true) ||
- typeof instance.UNSAFE_componentWillReceiveProps === 'function'
- ) {
- const componentName = getComponentName(workInProgress) || 'Component';
- if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
- warning(
- false,
- '%s: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. We recommend using ' +
- 'only getDerivedStateFromProps().',
- componentName,
- );
- didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
- }
- }
- }
-
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
diff --git a/packages/react-test-renderer/src/ReactShallowRenderer.js b/packages/react-test-renderer/src/ReactShallowRenderer.js
index b08732e0fd3..215f5b94d60 100644
--- a/packages/react-test-renderer/src/ReactShallowRenderer.js
+++ b/packages/react-test-renderer/src/ReactShallowRenderer.js
@@ -23,7 +23,7 @@ let didWarnAboutLegacyWillReceiveProps;
let didWarnAboutLegacyWillUpdate;
let didWarnAboutUndefinedDerivedState;
let didWarnAboutUninitializedState;
-let didWarnAboutWillReceivePropsAndDerivedState;
+let didWarnAboutLegacyLifecyclesAndDerivedState;
if (__DEV__) {
if (warnAboutDeprecatedLifecycles) {
@@ -33,7 +33,7 @@ if (__DEV__) {
}
didWarnAboutUndefinedDerivedState = {};
didWarnAboutUninitializedState = {};
- didWarnAboutWillReceivePropsAndDerivedState = {};
+ didWarnAboutLegacyLifecyclesAndDerivedState = {};
}
class ReactShallowRenderer {
@@ -352,23 +352,61 @@ class ReactShallowRenderer {
if (typeof type.getDerivedStateFromProps === 'function') {
if (__DEV__) {
- // Don't warn about react-lifecycles-compat polyfilled components
+ const instance = this._instance;
+
+ // If getDerivedStateFromProps() is 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.
+ let foundWillMountName = null;
+ let foundWillReceivePropsName = null;
+ let foundWillUpdateName = null;
if (
- (typeof this._instance.componentWillReceiveProps === 'function' &&
- this._instance.componentWillReceiveProps
- .__suppressDeprecationWarning !== true) ||
- typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
+ typeof instance.componentWillMount === 'function' &&
+ instance.componentWillMount.__suppressDeprecationWarning !== true
) {
- const componentName = getName(type, this._instance);
- if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
+ foundWillMountName = 'componentWillMount';
+ } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
+ foundWillMountName = 'UNSAFE_componentWillMount';
+ }
+ if (
+ typeof instance.componentWillReceiveProps === 'function' &&
+ instance.componentWillReceiveProps.__suppressDeprecationWarning !==
+ true
+ ) {
+ foundWillReceivePropsName = 'componentWillReceiveProps';
+ } else if (
+ typeof instance.UNSAFE_componentWillReceiveProps === 'function'
+ ) {
+ foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
+ }
+ if (typeof instance.componentWillUpdate === 'function') {
+ foundWillUpdateName = 'componentWillUpdate';
+ } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
+ foundWillUpdateName = 'UNSAFE_componentWillUpdate';
+ }
+ if (
+ foundWillMountName !== null ||
+ foundWillReceivePropsName !== null ||
+ foundWillUpdateName !== null
+ ) {
+ const componentName = getName(type, instance) || 'Component';
+ if (!didWarnAboutLegacyLifecyclesAndDerivedState[componentName]) {
warning(
false,
- '%s: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. We recommend using ' +
- 'only getDerivedStateFromProps().',
+ 'Unsafe legacy lifecycles will not be called for components using ' +
+ 'the new getDerivedStateFromProps() API.\n\n' +
+ '%s uses getDerivedStateFromProps() 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://fb.me/react-async-component-lifecycle-hooks',
componentName,
+ foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
+ foundWillReceivePropsName !== null
+ ? `\n ${foundWillReceivePropsName}`
+ : '',
+ foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
);
- didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
+ didWarnAboutLegacyLifecyclesAndDerivedState[componentName] = true;
}
}
}
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
index 2bf95824c32..287394290b3 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
@@ -125,8 +125,98 @@ describe('ReactShallowRenderer', () => {
}
const shallowRenderer = createRenderer();
- expect(() => shallowRenderer.render()).toWarnDev(
- 'Defines both componentWillReceiveProps() and static getDerivedStateFromProps()',
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.',
+ );
+ });
+
+ it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
+ let shallowRenderer;
+
+ class AllLegacyLifecycles extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {}
+ UNSAFE_componentWillReceiveProps() {}
+ componentWillUpdate() {}
+ render() {
+ return null;
+ }
+ }
+
+ shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillMount\n' +
+ ' UNSAFE_componentWillReceiveProps\n' +
+ ' componentWillUpdate\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillMount extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ UNSAFE_componentWillMount() {}
+ render() {
+ return null;
+ }
+ }
+
+ shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' UNSAFE_componentWillMount\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillMountAndUpdate extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {}
+ UNSAFE_componentWillUpdate() {}
+ render() {
+ return null;
+ }
+ }
+
+ shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillMount\n' +
+ ' UNSAFE_componentWillUpdate\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
+
+ class WillReceiveProps extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillReceiveProps() {}
+ render() {
+ return null;
+ }
+ }
+
+ shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillReceiveProps\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
);
});
@@ -1238,9 +1328,7 @@ describe('ReactShallowRenderer', () => {
const shallowRenderer = createRenderer();
expect(() => shallowRenderer.render()).toWarnDev(
- 'ComponentWithWarnings: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. We recommend using ' +
- 'only getDerivedStateFromProps().',
+ 'ComponentWithWarnings uses getDerivedStateFromProps() but also contains the following legacy lifecycles',
);
// Should not log duplicate warning
diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js
index c567b4fd853..3f15707f786 100644
--- a/packages/react/src/__tests__/createReactClassIntegration-test.js
+++ b/packages/react/src/__tests__/createReactClassIntegration-test.js
@@ -475,7 +475,15 @@ describe('create-react-class-integration', () => {
expect(() => {
ReactDOM.render(, document.createElement('div'));
- }).toWarnDev('Defines both componentWillReceiveProps');
+ }).toWarnDev(
+ 'Unsafe legacy lifecycles will not be called for components using the new getDerivedStateFromProps() API.\n\n' +
+ 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
+ ' componentWillMount\n' +
+ ' componentWillReceiveProps\n' +
+ ' componentWillUpdate\n\n' +
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
+ 'https://fb.me/react-async-component-lifecycle-hooks',
+ );
ReactDOM.render(, document.createElement('div'));
});