diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js index c124cc55b6647..aa7fbf73c1a02 100644 --- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js @@ -38,19 +38,21 @@ describe('ReactComponentLifeCycle', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(, container)).toWarnDev([ - 'Warning: MyComponent: componentWillMount() is deprecated and will be ' + - 'removed in the next major version.', + 'componentWillMount is deprecated and will be removed in the next major version. ' + + 'Use componentDidMount instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillMount.' + + '\n\nPlease update the following components: MyComponent', + 'componentWillReceiveProps is deprecated and will be removed in the next major version. ' + + 'Use static getDerivedStateFromProps instead.' + + '\n\nPlease update the following components: MyComponent', + 'componentWillUpdate is deprecated and will be removed in the next major version. ' + + 'Use componentDidUpdate instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillUpdate.' + + '\n\nPlease update the following components: MyComponent', ]); - expect(() => ReactDOM.render(, container)).toWarnDev([ - 'Warning: MyComponent: componentWillReceiveProps() is deprecated and ' + - 'will be removed in the next major version.', - 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' + - 'removed in the next major version.', - ]); - - // Dedupe check (instantiate and update) + // Dedupe check (update and instantiate new + ReactDOM.render(, container); ReactDOM.render(, container); - ReactDOM.render(, container); }); }); diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 8f8735ae8167f..ddd5a9cde2aa5 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -42,9 +42,6 @@ import {hasContextChanged} from './ReactFiberContext'; const fakeInternalInstance = {}; const isArray = Array.isArray; -let didWarnAboutLegacyWillMount; -let didWarnAboutLegacyWillReceiveProps; -let didWarnAboutLegacyWillUpdate; let didWarnAboutStateAssignmentForComponent; let didWarnAboutUndefinedDerivedState; let didWarnAboutUninitializedState; @@ -52,11 +49,6 @@ let didWarnAboutWillReceivePropsAndDerivedState; let warnOnInvalidCallback; if (__DEV__) { - if (warnAboutDeprecatedLifecycles) { - didWarnAboutLegacyWillMount = {}; - didWarnAboutLegacyWillReceiveProps = {}; - didWarnAboutLegacyWillUpdate = {}; - } didWarnAboutStateAssignmentForComponent = {}; didWarnAboutUndefinedDerivedState = {}; didWarnAboutUninitializedState = {}; @@ -462,25 +454,6 @@ export default function( const oldState = instance.state; if (typeof instance.componentWillMount === 'function') { - if (__DEV__) { - if (warnAboutDeprecatedLifecycles) { - const componentName = getComponentName(workInProgress) || 'Component'; - if (!didWarnAboutLegacyWillMount[componentName]) { - warning( - false, - '%s: componentWillMount() is deprecated and will be ' + - 'removed in the next major version. Read about the motivations ' + - 'behind this change: ' + - 'https://fb.me/react-async-component-lifecycle-hooks' + - '\n\n' + - 'As a temporary workaround, you can rename to ' + - 'UNSAFE_componentWillMount instead.', - componentName, - ); - didWarnAboutLegacyWillMount[componentName] = true; - } - } - } instance.componentWillMount(); } else { instance.UNSAFE_componentWillMount(); @@ -510,27 +483,6 @@ export default function( ) { const oldState = instance.state; if (typeof instance.componentWillReceiveProps === 'function') { - if (__DEV__) { - if (warnAboutDeprecatedLifecycles) { - const componentName = getComponentName(workInProgress) || 'Component'; - if (!didWarnAboutLegacyWillReceiveProps[componentName]) { - warning( - false, - '%s: componentWillReceiveProps() is deprecated and ' + - 'will be removed in the next major version. Use ' + - 'static getDerivedStateFromProps() instead. Read about the ' + - 'motivations behind this change: ' + - 'https://fb.me/react-async-component-lifecycle-hooks' + - '\n\n' + - 'As a temporary workaround, you can rename to ' + - 'UNSAFE_componentWillReceiveProps instead.', - componentName, - ); - didWarnAboutLegacyWillReceiveProps[componentName] = true; - } - } - } - startPhaseTimer(workInProgress, 'componentWillReceiveProps'); instance.componentWillReceiveProps(newProps, newContext); stopPhaseTimer(); @@ -652,7 +604,14 @@ export default function( if (__DEV__) { if (workInProgress.internalContextTag & StrictMode) { - ReactStrictModeWarnings.recordLifecycleWarnings( + ReactStrictModeWarnings.recordUnsafeLifecycleWarnings( + workInProgress, + instance, + ); + } + + if (warnAboutDeprecatedLifecycles) { + ReactStrictModeWarnings.recordDeprecationWarnings( workInProgress, instance, ); @@ -893,27 +852,6 @@ export default function( typeof instance.componentWillUpdate === 'function' ) { if (typeof instance.componentWillUpdate === 'function') { - if (__DEV__) { - if (warnAboutDeprecatedLifecycles) { - const componentName = - getComponentName(workInProgress) || 'Component'; - if (!didWarnAboutLegacyWillUpdate[componentName]) { - warning( - false, - '%s: componentWillUpdate() is deprecated and will be ' + - 'removed in the next major version. Read about the motivations ' + - 'behind this change: ' + - 'https://fb.me/react-async-component-lifecycle-hooks' + - '\n\n' + - 'As a temporary workaround, you can rename to ' + - 'UNSAFE_componentWillUpdate instead.', - componentName, - ); - didWarnAboutLegacyWillUpdate[componentName] = true; - } - } - } - startPhaseTimer(workInProgress, 'componentWillUpdate'); instance.componentWillUpdate(newProps, newState, newContext); stopPhaseTimer(); diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index a2d0a5d354d59..77c967eb85399 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -34,7 +34,10 @@ import { HostPortal, ClassComponent, } from 'shared/ReactTypeOfWork'; -import {enableUserTimingAPI} from 'shared/ReactFeatureFlags'; +import { + enableUserTimingAPI, + warnAboutDeprecatedLifecycles, +} from 'shared/ReactFeatureFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -312,7 +315,11 @@ export default function( function commitAllLifeCycles() { if (__DEV__) { - ReactStrictModeWarnings.flushPendingAsyncWarnings(); + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings(); + + if (warnAboutDeprecatedLifecycles) { + ReactStrictModeWarnings.flushPendingDeprecationWarnings(); + } } while (nextEffect !== null) { diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 2249d677f90d9..fb1b15d3d1618 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -23,8 +23,10 @@ type FiberToLifecycleMap = Map; const ReactStrictModeWarnings = { discardPendingWarnings(): void {}, - flushPendingAsyncWarnings(): void {}, - recordLifecycleWarnings(fiber: Fiber, instance: any): void {}, + flushPendingDeprecationWarnings(): void {}, + flushPendingUnsafeLifecycleWarnings(): void {}, + recordDeprecationWarnings(fiber: Fiber, instance: any): void {}, + recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {}, }; if (__DEV__) { @@ -34,17 +36,24 @@ if (__DEV__) { UNSAFE_componentWillUpdate: 'componentDidUpdate', }; - let pendingWarningsMap: FiberToLifecycleMap = new Map(); + let pendingComponentWillMountWarnings: Array = []; + let pendingComponentWillReceivePropsWarnings: Array = []; + let pendingComponentWillUpdateWarnings: Array = []; + let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map(); // Tracks components we have already warned about. - const didWarnSet = new Set(); + const didWarnAboutDeprecatedLifecycles = new Set(); + const didWarnAboutUnsafeLifecycles = new Set(); ReactStrictModeWarnings.discardPendingWarnings = () => { - pendingWarningsMap = new Map(); + pendingComponentWillMountWarnings = []; + pendingComponentWillReceivePropsWarnings = []; + pendingComponentWillUpdateWarnings = []; + pendingUnsafeLifecycleWarnings = new Map(); }; - ReactStrictModeWarnings.flushPendingAsyncWarnings = () => { - ((pendingWarningsMap: any): FiberToLifecycleMap).forEach( + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => { + ((pendingUnsafeLifecycleWarnings: any): FiberToLifecycleMap).forEach( (lifecycleWarningsMap, strictRoot) => { const lifecyclesWarningMesages = []; @@ -54,7 +63,7 @@ if (__DEV__) { const componentNames = new Set(); lifecycleWarnings.forEach(fiber => { componentNames.add(getComponentName(fiber) || 'Component'); - didWarnSet.add(fiber.type); + didWarnAboutUnsafeLifecycles.add(fiber.type); }); const formatted = lifecycle.replace('UNSAFE_', ''); @@ -88,7 +97,7 @@ if (__DEV__) { }, ); - pendingWarningsMap = new Map(); + pendingUnsafeLifecycleWarnings = new Map(); }; const getStrictRoot = (fiber: Fiber): Fiber => { @@ -105,7 +114,103 @@ if (__DEV__) { return maybeStrictRoot; }; - ReactStrictModeWarnings.recordLifecycleWarnings = ( + ReactStrictModeWarnings.flushPendingDeprecationWarnings = () => { + if (pendingComponentWillMountWarnings.length > 0) { + const uniqueNames = new Set(); + pendingComponentWillMountWarnings.forEach(fiber => { + uniqueNames.add(getComponentName(fiber) || 'Component'); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); + + const sortedNames = Array.from(uniqueNames) + .sort() + .join(', '); + + warning( + false, + 'componentWillMount is deprecated and will be removed in the next major version. ' + + 'Use componentDidMount instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillMount.' + + '\n\nPlease update the following components: %s' + + '\n\nLearn more about this warning here:' + + '\nhttps://fb.me/react-async-component-lifecycle-hooks', + sortedNames, + ); + + pendingComponentWillMountWarnings = []; + } + + if (pendingComponentWillReceivePropsWarnings.length > 0) { + const uniqueNames = new Set(); + pendingComponentWillReceivePropsWarnings.forEach(fiber => { + uniqueNames.add(getComponentName(fiber) || 'Component'); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); + + const sortedNames = Array.from(uniqueNames) + .sort() + .join(', '); + + warning( + false, + 'componentWillReceiveProps is deprecated and will be removed in the next major version. ' + + 'Use static getDerivedStateFromProps instead.' + + '\n\nPlease update the following components: %s' + + '\n\nLearn more about this warning here:' + + '\nhttps://fb.me/react-async-component-lifecycle-hooks', + sortedNames, + ); + + pendingComponentWillReceivePropsWarnings = []; + } + + if (pendingComponentWillUpdateWarnings.length > 0) { + const uniqueNames = new Set(); + pendingComponentWillUpdateWarnings.forEach(fiber => { + uniqueNames.add(getComponentName(fiber) || 'Component'); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); + + const sortedNames = Array.from(uniqueNames) + .sort() + .join(', '); + + warning( + false, + 'componentWillUpdate is deprecated and will be removed in the next major version. ' + + 'Use componentDidUpdate instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillUpdate.' + + '\n\nPlease update the following components: %s' + + '\n\nLearn more about this warning here:' + + '\nhttps://fb.me/react-async-component-lifecycle-hooks', + sortedNames, + ); + + pendingComponentWillUpdateWarnings = []; + } + }; + + ReactStrictModeWarnings.recordDeprecationWarnings = ( + fiber: Fiber, + instance: any, + ) => { + // Dedup strategy: Warn once per component. + if (didWarnAboutDeprecatedLifecycles.has(fiber.type)) { + return; + } + + if (typeof instance.componentWillMount === 'function') { + pendingComponentWillMountWarnings.push(fiber); + } + if (typeof instance.componentWillReceiveProps === 'function') { + pendingComponentWillReceivePropsWarnings.push(fiber); + } + if (typeof instance.componentWillUpdate === 'function') { + pendingComponentWillUpdateWarnings.push(fiber); + } + }; + + ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = ( fiber: Fiber, instance: any, ) => { @@ -116,21 +221,21 @@ if (__DEV__) { // are often vague and are likely to collide between 3rd party libraries. // An expand property is probably okay to use here since it's DEV-only, // and will only be set in the event of serious warnings. - if (didWarnSet.has(fiber.type)) { + if (didWarnAboutUnsafeLifecycles.has(fiber.type)) { return; } let warningsForRoot; - if (!pendingWarningsMap.has(strictRoot)) { + if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) { warningsForRoot = { UNSAFE_componentWillMount: [], UNSAFE_componentWillReceiveProps: [], UNSAFE_componentWillUpdate: [], }; - pendingWarningsMap.set(strictRoot, warningsForRoot); + pendingUnsafeLifecycleWarnings.set(strictRoot, warningsForRoot); } else { - warningsForRoot = pendingWarningsMap.get(strictRoot); + warningsForRoot = pendingUnsafeLifecycleWarnings.get(strictRoot); } const unsafeLifecycles = []; diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.internal.js b/packages/react/src/__tests__/createReactClassIntegration-test.internal.js index e886b7867c94d..fae64dca9c673 100644 --- a/packages/react/src/__tests__/createReactClassIntegration-test.internal.js +++ b/packages/react/src/__tests__/createReactClassIntegration-test.internal.js @@ -95,14 +95,18 @@ describe('create-react-class-integration', () => { 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' + 'clean up subscriptions and pending requests in componentWillUnmount ' + 'to prevent memory leaks.', - 'Warning: MyComponent: componentWillMount() is deprecated and will be ' + - 'removed in the next major version.', + 'componentWillMount is deprecated and will be removed in the next major version. ' + + 'Use componentDidMount instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillMount.' + + '\n\nPlease update the following components: MyComponent', + 'componentWillUpdate is deprecated and will be removed in the next major version. ' + + 'Use componentDidUpdate instead. As a temporary workaround, ' + + 'you can rename to UNSAFE_componentWillUpdate.' + + '\n\nPlease update the following components: MyComponent', ]); - expect(() => ReactDOM.render(, container)).toWarnDev( - 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' + - 'removed in the next major version.', - ); + // Dedupe + ReactDOM.render(, container); ReactDOM.unmountComponentAtNode(container); instance.log('after unmount');