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')); });