From e7127aa10d981bb6a615004692f49a5d528e4dcb Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 10 Jul 2019 19:36:04 +0100 Subject: [PATCH] unify deprecated/unsafe lifecycle warnings, pass tests - redoes #15431 from scratch, taking on the feedback there - unifies the messaging between "deprecated" and UNSAFE_ lifecycle messages. It reorganizes ReactStrictModeWarnings.js to capture and flush all the lifecycle warnings in one procedure each. - matches the warning from ReactPartialRenderer to match the above change - passes all the tests - this also turns on `warnAboutDeprecatedLifecycles` for the test renderer. I think we missed doing so it previously. In a future PR, I'll remove the feature flag altogether. - this DOES NOT do the same treatment for context warnings, I'll do that in another PR too --- .../__tests__/ReactComponentLifeCycle-test.js | 50 +- .../ReactDOMServerLifecycles-test.js | 17 +- .../ReactServerRenderingHydration-test.js | 18 +- .../src/server/ReactPartialRenderer.js | 13 +- .../src/ReactFiberClassComponent.js | 5 - .../src/ReactFiberWorkLoop.js | 1 - .../src/ReactStrictModeWarnings.js | 430 +++++++++--------- .../ReactIncremental-test.internal.js | 21 +- .../ReactIncrementalPerf-test.internal.js | 18 +- ...eactIncrementalReflection-test.internal.js | 14 +- .../ReactIncrementalUpdates-test.internal.js | 3 +- .../ReactStrictMode-test.internal.js | 86 +--- ...eateReactClassIntegration-test.internal.js | 9 +- .../createReactClassIntegration-test.js | 18 +- .../forks/ReactFeatureFlags.test-renderer.js | 2 +- scripts/print-warnings/print-warnings.js | 13 +- 16 files changed, 324 insertions(+), 394 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js index fd286f1e68c8e..3c6d08110324a 100644 --- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js +++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js @@ -709,9 +709,9 @@ describe('ReactComponentLifeCycle', () => { ); }).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); @@ -748,9 +748,9 @@ describe('ReactComponentLifeCycle', () => { ); }).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); @@ -815,7 +815,10 @@ describe('ReactComponentLifeCycle', () => { {withoutStack: true}, ); }).toLowPriorityWarnDev( - ['componentWillMount is deprecated', 'componentWillUpdate is deprecated'], + [ + 'componentWillMount has been renamed', + 'componentWillUpdate has been renamed', + ], {withoutStack: true}, ); @@ -863,7 +866,7 @@ describe('ReactComponentLifeCycle', () => { 'https://fb.me/react-async-component-lifecycle-hooks', {withoutStack: true}, ); - }).toLowPriorityWarnDev(['componentWillMount is deprecated'], { + }).toLowPriorityWarnDev(['componentWillMount has been renamed'], { withoutStack: true, }); @@ -887,7 +890,7 @@ describe('ReactComponentLifeCycle', () => { 'https://fb.me/react-async-component-lifecycle-hooks', {withoutStack: true}, ); - }).toLowPriorityWarnDev(['componentWillReceiveProps is deprecated'], { + }).toLowPriorityWarnDev(['componentWillReceiveProps has been renamed'], { withoutStack: true, }); }); @@ -921,7 +924,10 @@ describe('ReactComponentLifeCycle', () => { {withoutStack: true}, ); }).toLowPriorityWarnDev( - ['componentWillMount is deprecated', 'componentWillUpdate is deprecated'], + [ + 'componentWillMount has been renamed', + 'componentWillUpdate has been renamed', + ], {withoutStack: true}, ); @@ -967,7 +973,7 @@ describe('ReactComponentLifeCycle', () => { 'https://fb.me/react-async-component-lifecycle-hooks', {withoutStack: true}, ); - }).toLowPriorityWarnDev(['componentWillMount is deprecated'], { + }).toLowPriorityWarnDev(['componentWillMount has been renamed'], { withoutStack: true, }); @@ -990,7 +996,7 @@ describe('ReactComponentLifeCycle', () => { 'https://fb.me/react-async-component-lifecycle-hooks', {withoutStack: true}, ); - }).toLowPriorityWarnDev(['componentWillReceiveProps is deprecated'], { + }).toLowPriorityWarnDev(['componentWillReceiveProps has been renamed'], { withoutStack: true, }); }); @@ -1130,9 +1136,9 @@ describe('ReactComponentLifeCycle', () => { ReactDOM.render(, div), ).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); @@ -1403,17 +1409,9 @@ describe('ReactComponentLifeCycle', () => { ReactDOM.render(, container), ).toLowPriorityWarnDev( [ - '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', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js index 76230a3230350..4aed0e0246a69 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js @@ -229,7 +229,7 @@ describe('ReactDOMServerLifecycles', () => { expect(() => ReactDOMServer.renderToString(), - ).toLowPriorityWarnDev('componentWillMount() is deprecated', { + ).toLowPriorityWarnDev('componentWillMount has been renamed', { withoutStack: true, }); expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']); @@ -286,10 +286,9 @@ describe('ReactDOMServerLifecycles', () => { expect(() => ReactDOMServer.renderToString(), - ).toLowPriorityWarnDev( - 'Component: componentWillMount() is deprecated and will be removed in the next major version.', - {withoutStack: true}, - ); + ).toLowPriorityWarnDev('componentWillMount has been renamed', { + withoutStack: true, + }); }); it('should warn about deprecated lifecycle hooks', () => { @@ -302,11 +301,9 @@ describe('ReactDOMServerLifecycles', () => { expect(() => ReactDOMServer.renderToString(), - ).toLowPriorityWarnDev( - 'Warning: Component: componentWillMount() is deprecated and will be removed ' + - 'in the next major version.', - {withoutStack: true}, - ); + ).toLowPriorityWarnDev('componentWillMount has been renamed', { + withoutStack: true, + }); // De-duped ReactDOMServer.renderToString(); diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js index 38d300debd14e..d093a11bf8ab2 100644 --- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js @@ -362,20 +362,16 @@ describe('ReactDOMServerHydration', () => { const element = document.createElement('div'); expect(() => { element.innerHTML = ReactDOMServer.renderToString(markup); - }).toLowPriorityWarnDev( - ['componentWillMount() is deprecated and will be removed'], - {withoutStack: true}, - ); + }).toLowPriorityWarnDev(['componentWillMount has been renamed'], { + withoutStack: true, + }); expect(element.textContent).toBe('Hi'); expect(() => { - expect(() => ReactDOM.hydrate(markup, element)).toWarnDev( - 'Please update the following components to use componentDidMount instead: ComponentWithWarning', - ); - }).toLowPriorityWarnDev( - ['componentWillMount is deprecated and will be removed'], - {withoutStack: true}, - ); + ReactDOM.hydrate(markup, element); + }).toLowPriorityWarnDev(['componentWillMount has been renamed'], { + withoutStack: true, + }); expect(element.textContent).toBe('Hi'); }); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index fdc7e17b3aa1a..7e0683a153a7b 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -572,13 +572,12 @@ function resolve( if (!didWarnAboutDeprecatedWillMount[componentName]) { lowPriorityWarning( 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.', + // keep this warning in sync with ReactStrictModeWarning.js + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move component logic from componentWillMount into componentDidMount (preferred in most cases) ' + + 'or the constructor.\n' + + '\nPlease update the following components: %s', componentName, ); didWarnAboutDeprecatedWillMount[componentName] = true; diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 369b01dbca350..db35d7fe714b0 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -806,11 +806,6 @@ function mountClassInstance( } if (workInProgress.mode & StrictMode) { - ReactStrictModeWarnings.recordUnsafeLifecycleWarnings( - workInProgress, - instance, - ); - ReactStrictModeWarnings.recordLegacyContextWarning( workInProgress, instance, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index b05e42a5512d9..7c487d177553d 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -2247,7 +2247,6 @@ function checkForNestedUpdates() { function flushRenderPhaseStrictModeWarningsInDEV() { if (__DEV__) { - ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings(); ReactStrictModeWarnings.flushLegacyContextWarning(); if (warnAboutDeprecatedLifecycles) { diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 143e7ebd4a7b9..caf989195a5de 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -16,42 +16,31 @@ import {StrictMode} from './ReactTypeOfMode'; import lowPriorityWarning from 'shared/lowPriorityWarning'; import warningWithoutStack from 'shared/warningWithoutStack'; -type LIFECYCLE = - | 'UNSAFE_componentWillMount' - | 'UNSAFE_componentWillReceiveProps' - | 'UNSAFE_componentWillUpdate'; -type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array}; -type FiberToLifecycleMap = Map; type FiberArray = Array; type FiberToFiberComponentsMap = Map; const ReactStrictModeWarnings = { - discardPendingWarnings(): void {}, - flushPendingDeprecationWarnings(): void {}, - flushPendingUnsafeLifecycleWarnings(): void {}, recordDeprecationWarnings(fiber: Fiber, instance: any): void {}, - recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {}, + flushPendingDeprecationWarnings(): void {}, recordLegacyContextWarning(fiber: Fiber, instance: any): void {}, flushLegacyContextWarning(): void {}, + discardPendingWarnings(): void {}, }; if (__DEV__) { - const LIFECYCLE_SUGGESTIONS = { - UNSAFE_componentWillMount: 'componentDidMount', - UNSAFE_componentWillReceiveProps: 'static getDerivedStateFromProps', - UNSAFE_componentWillUpdate: 'componentDidUpdate', - }; + const findStrictRoot = (fiber: Fiber): Fiber | null => { + let maybeStrictRoot = null; - let pendingComponentWillMountWarnings: Array = []; - let pendingComponentWillReceivePropsWarnings: Array = []; - let pendingComponentWillUpdateWarnings: Array = []; - let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map(); - let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map(); + let node = fiber; + while (node !== null) { + if (node.mode & StrictMode) { + maybeStrictRoot = node; + } + node = node.return; + } - // Tracks components we have already warned about. - const didWarnAboutDeprecatedLifecycles = new Set(); - const didWarnAboutUnsafeLifecycles = new Set(); - const didWarnAboutLegacyContext = new Set(); + return maybeStrictRoot; + }; const setToSortedString = set => { const array = []; @@ -61,243 +50,256 @@ if (__DEV__) { return array.sort().join(', '); }; - ReactStrictModeWarnings.discardPendingWarnings = () => { - pendingComponentWillMountWarnings = []; - pendingComponentWillReceivePropsWarnings = []; - pendingComponentWillUpdateWarnings = []; - pendingUnsafeLifecycleWarnings = new Map(); - pendingLegacyContextWarning = new Map(); - }; + let pendingComponentWillMountWarnings: Array = []; + let pendingUNSAFE_ComponentWillMountWarnings: Array = []; + let pendingComponentWillReceivePropsWarnings: Array = []; + let pendingUNSAFE_ComponentWillReceivePropsWarnings: Array = []; + let pendingComponentWillUpdateWarnings: Array = []; + let pendingUNSAFE_ComponentWillUpdateWarnings: Array = []; - ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => { - ((pendingUnsafeLifecycleWarnings: any): FiberToLifecycleMap).forEach( - (lifecycleWarningsMap, strictRoot) => { - const lifecyclesWarningMessages = []; - - Object.keys(lifecycleWarningsMap).forEach(lifecycle => { - const lifecycleWarnings = lifecycleWarningsMap[lifecycle]; - if (lifecycleWarnings.length > 0) { - const componentNames = new Set(); - lifecycleWarnings.forEach(fiber => { - componentNames.add(getComponentName(fiber.type) || 'Component'); - didWarnAboutUnsafeLifecycles.add(fiber.type); - }); - - const formatted = lifecycle.replace('UNSAFE_', ''); - const suggestion = LIFECYCLE_SUGGESTIONS[lifecycle]; - const sortedComponentNames = setToSortedString(componentNames); - - lifecyclesWarningMessages.push( - `${formatted}: Please update the following components to use ` + - `${suggestion} instead: ${sortedComponentNames}`, - ); - } - }); + // Tracks components we have already warned about. + const didWarnAboutDeprecatedLifecycles = new Set(); - if (lifecyclesWarningMessages.length > 0) { - const strictRootComponentStack = getStackByFiberInDevAndProd( - strictRoot, - ); - - warningWithoutStack( - false, - 'Unsafe lifecycle methods were found within a strict-mode tree:%s' + - '\n\n%s' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', - strictRootComponentStack, - lifecyclesWarningMessages.join('\n\n'), - ); - } - }, - ); + ReactStrictModeWarnings.recordDeprecationWarnings = ( + fiber: Fiber, + instance: any, + ) => { + // Dedup strategy: Warn once per component. + if (didWarnAboutDeprecatedLifecycles.has(fiber.type)) { + return; + } - pendingUnsafeLifecycleWarnings = new Map(); - }; + if ( + typeof instance.componentWillMount === 'function' && + // Don't warn about react-lifecycles-compat polyfilled components. + instance.componentWillMount.__suppressDeprecationWarning !== true + ) { + pendingComponentWillMountWarnings.push(fiber); + } - const findStrictRoot = (fiber: Fiber): Fiber | null => { - let maybeStrictRoot = null; + if ( + fiber.mode & StrictMode && + typeof instance.UNSAFE_componentWillMount === 'function' + ) { + pendingUNSAFE_ComponentWillMountWarnings.push(fiber); + } - let node = fiber; - while (node !== null) { - if (node.mode & StrictMode) { - maybeStrictRoot = node; - } - node = node.return; + if ( + typeof instance.componentWillReceiveProps === 'function' && + instance.componentWillReceiveProps.__suppressDeprecationWarning !== true + ) { + pendingComponentWillReceivePropsWarnings.push(fiber); } - return maybeStrictRoot; + if ( + fiber.mode & StrictMode && + typeof instance.UNSAFE_componentWillReceiveProps === 'function' + ) { + pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber); + } + + if ( + typeof instance.componentWillUpdate === 'function' && + instance.componentWillUpdate.__suppressDeprecationWarning !== true + ) { + pendingComponentWillUpdateWarnings.push(fiber); + } + + if ( + fiber.mode & StrictMode && + typeof instance.UNSAFE_componentWillUpdate === 'function' + ) { + pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber); + } }; ReactStrictModeWarnings.flushPendingDeprecationWarnings = () => { + // We do an initial pass to gather component names + const componentWillMountUniqueNames = new Set(); if (pendingComponentWillMountWarnings.length > 0) { - const uniqueNames = new Set(); pendingComponentWillMountWarnings.forEach(fiber => { - uniqueNames.add(getComponentName(fiber.type) || 'Component'); + componentWillMountUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); didWarnAboutDeprecatedLifecycles.add(fiber.type); }); - - const sortedNames = setToSortedString(uniqueNames); - - lowPriorityWarning( - 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 = []; } + const UNSAFE_componentWillMountUniqueNames = new Set(); + if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) { + pendingUNSAFE_ComponentWillMountWarnings.forEach(fiber => { + UNSAFE_componentWillMountUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); + pendingUNSAFE_ComponentWillMountWarnings = []; + } + + const componentWillReceivePropsUniqueNames = new Set(); if (pendingComponentWillReceivePropsWarnings.length > 0) { - const uniqueNames = new Set(); pendingComponentWillReceivePropsWarnings.forEach(fiber => { - uniqueNames.add(getComponentName(fiber.type) || 'Component'); + componentWillReceivePropsUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); didWarnAboutDeprecatedLifecycles.add(fiber.type); }); - const sortedNames = setToSortedString(uniqueNames); + pendingComponentWillReceivePropsWarnings = []; + } - lowPriorityWarning( - 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, - ); + const UNSAFE_componentWillReceivePropsUniqueNames = new Set(); + if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) { + pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(fiber => { + UNSAFE_componentWillReceivePropsUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); - pendingComponentWillReceivePropsWarnings = []; + pendingUNSAFE_ComponentWillReceivePropsWarnings = []; } + const componentWillUpdateUniqueNames = new Set(); if (pendingComponentWillUpdateWarnings.length > 0) { - const uniqueNames = new Set(); pendingComponentWillUpdateWarnings.forEach(fiber => { - uniqueNames.add(getComponentName(fiber.type) || 'Component'); + componentWillUpdateUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); didWarnAboutDeprecatedLifecycles.add(fiber.type); }); - const sortedNames = setToSortedString(uniqueNames); - - lowPriorityWarning( - 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; - } + const UNSAFE_componentWillUpdateUniqueNames = new Set(); + if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) { + pendingUNSAFE_ComponentWillUpdateWarnings.forEach(fiber => { + UNSAFE_componentWillUpdateUniqueNames.add( + getComponentName(fiber.type) || 'Component', + ); + didWarnAboutDeprecatedLifecycles.add(fiber.type); + }); - // Don't warn about react-lifecycles-compat polyfilled components. - if ( - typeof instance.componentWillMount === 'function' && - instance.componentWillMount.__suppressDeprecationWarning !== true - ) { - pendingComponentWillMountWarnings.push(fiber); - } - if ( - typeof instance.componentWillReceiveProps === 'function' && - instance.componentWillReceiveProps.__suppressDeprecationWarning !== true - ) { - pendingComponentWillReceivePropsWarnings.push(fiber); - } - if ( - typeof instance.componentWillUpdate === 'function' && - instance.componentWillUpdate.__suppressDeprecationWarning !== true - ) { - pendingComponentWillUpdateWarnings.push(fiber); + pendingUNSAFE_ComponentWillUpdateWarnings = []; } - }; - ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = ( - fiber: Fiber, - instance: any, - ) => { - const strictRoot = findStrictRoot(fiber); - if (strictRoot === null) { + // Finally, we flush all the warnings + // UNSAFE_ ones before the deprecated ones, since they'll be 'louder' + if (UNSAFE_componentWillMountUniqueNames.size > 0) { + const sortedNames = setToSortedString( + UNSAFE_componentWillMountUniqueNames, + ); warningWithoutStack( false, - 'Expected to find a StrictMode component in a strict mode tree. ' + - 'This error is likely caused by a bug in React. Please file an issue.', + 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move component logic from UNSAFE_componentWillMount into componentDidMount (preferred in most cases) ' + + 'or the constructor.\n' + + '\nPlease update the following components: %s', + sortedNames, ); - return; } - // Dedup strategy: Warn once per component. - // This is difficult to track any other way since component names - // 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 (didWarnAboutUnsafeLifecycles.has(fiber.type)) { - return; + if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) { + const sortedNames = setToSortedString( + UNSAFE_componentWillReceivePropsUniqueNames, + ); + warningWithoutStack( + false, + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' + + 'and may indicate bugs in your code. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move data fetching logic or side effects from UNSAFE_componentWillReceiveProps ' + + 'into componentDidUpdate.\n' + + "* If you're updating state whenever props change, " + + 'move this logic into static getDerivedStateFromProps, or refactor your ' + + 'code to use memoization techniques. ' + + 'Learn more at: https://fb.me/react-derived-state\n' + + '\nPlease update the following components: %s', + sortedNames, + ); } - let warningsForRoot; - if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) { - warningsForRoot = { - UNSAFE_componentWillMount: [], - UNSAFE_componentWillReceiveProps: [], - UNSAFE_componentWillUpdate: [], - }; - - pendingUnsafeLifecycleWarnings.set(strictRoot, warningsForRoot); - } else { - warningsForRoot = pendingUnsafeLifecycleWarnings.get(strictRoot); + if (UNSAFE_componentWillUpdateUniqueNames.size > 0) { + const sortedNames = setToSortedString( + UNSAFE_componentWillUpdateUniqueNames, + ); + warningWithoutStack( + false, + 'Using UNSAFE_componentWillUpdate in strict mode is not recommended ' + + 'and may indicate bugs in your code. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move data fetching logic or side effects from UNSAFE_componentWillUpdate into componentDidUpdate.\n' + + '\nPlease update the following components: %s', + sortedNames, + ); } - const unsafeLifecycles = []; - if ( - (typeof instance.componentWillMount === 'function' && - instance.componentWillMount.__suppressDeprecationWarning !== true) || - typeof instance.UNSAFE_componentWillMount === 'function' - ) { - unsafeLifecycles.push('UNSAFE_componentWillMount'); - } - if ( - (typeof instance.componentWillReceiveProps === 'function' && - instance.componentWillReceiveProps.__suppressDeprecationWarning !== - true) || - typeof instance.UNSAFE_componentWillReceiveProps === 'function' - ) { - unsafeLifecycles.push('UNSAFE_componentWillReceiveProps'); + if (componentWillMountUniqueNames.size > 0) { + const sortedNames = setToSortedString(componentWillMountUniqueNames); + + lowPriorityWarning( + false, + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move component logic from componentWillMount into componentDidMount (preferred in most cases) ' + + 'or the constructor.\n' + + '* Rename componentWillMount in your code to UNSAFE_componentWillMount to suppress ' + + 'this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work.\n' + + '* To rename all deprecated lifecycles to their new names, you can run ' + + '`npx react-codemod rename-unsafe-lifecycles /path/to/code`.\n' + + '\nPlease update the following components: %s', + sortedNames, + ); } - if ( - (typeof instance.componentWillUpdate === 'function' && - instance.componentWillUpdate.__suppressDeprecationWarning !== true) || - typeof instance.UNSAFE_componentWillUpdate === 'function' - ) { - unsafeLifecycles.push('UNSAFE_componentWillUpdate'); + + if (componentWillReceivePropsUniqueNames.size > 0) { + const sortedNames = setToSortedString( + componentWillReceivePropsUniqueNames, + ); + + lowPriorityWarning( + false, + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move data fetching logic or side effects from componentWillReceiveProps into componentDidUpdate.\n' + + "* If you're updating state whenever props change, refactor your " + + 'code to use memoization techniques, or move this logic into ' + + 'static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state\n' + + '* Rename componentWillReceiveProps in your code to UNSAFE_componentWillReceiveProps to suppress ' + + 'this warning in non-strict mode.. In React 17.x, only the UNSAFE_ name will work.\n' + + '* To rename all deprecated lifecycles to their new names, you can run ' + + '`npx react-codemod rename-unsafe-lifecycles /path/to/code`.\n' + + '\nPlease update the following components: %s', + sortedNames, + ); } - if (unsafeLifecycles.length > 0) { - unsafeLifecycles.forEach(lifecycle => { - ((warningsForRoot: any): LifecycleToComponentsMap)[lifecycle].push( - fiber, - ); - }); + if (componentWillUpdateUniqueNames.size > 0) { + const sortedNames = setToSortedString(componentWillUpdateUniqueNames); + + lowPriorityWarning( + false, + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://fb.me/react-async-component-lifecycle-hooks for details.\n\n' + + '* Move data fetching logic or side effects from componentWillUpdate into componentDidUpdate.\n' + + '* Rename componentWillUpdate in your code to UNSAFE_componentWillUpdate to suppress ' + + 'this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work.\n' + + '* To rename all deprecated lifecycles to their new names, you can run ' + + '`npx react-codemod rename-unsafe-lifecycles /path/to/code`.\n' + + '\nPlease update the following components: %s', + sortedNames, + ); } }; + let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map(); + + // Tracks components we have already warned about. + const didWarnAboutLegacyContext = new Set(); + ReactStrictModeWarnings.recordLegacyContextWarning = ( fiber: Fiber, instance: any, @@ -358,6 +360,16 @@ if (__DEV__) { }, ); }; + + ReactStrictModeWarnings.discardPendingWarnings = () => { + pendingComponentWillMountWarnings = []; + pendingUNSAFE_ComponentWillMountWarnings = []; + pendingComponentWillReceivePropsWarnings = []; + pendingUNSAFE_ComponentWillReceivePropsWarnings = []; + pendingComponentWillUpdateWarnings = []; + pendingUNSAFE_ComponentWillUpdateWarnings = []; + pendingLegacyContextWarning = new Map(); + }; } export default ReactStrictModeWarnings; diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js index 6557eb0b1dc88..3671f0e759f44 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js @@ -2446,8 +2446,7 @@ describe('ReactIncremental', () => { ReactNoop.render(); expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev( [ - 'componentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: MyComponent', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', 'Legacy context API has been detected within a strict-mode tree: \n\n' + 'Please update the following components: MyComponent', ], @@ -2887,8 +2886,7 @@ describe('ReactIncremental', () => { expect(Scheduler).toFlushAndYield([]); }); - // We don't currently use fibers as keys. Re-enable this test if we - // ever do again. + // We sometimes use Maps with Fibers as keys. it('does not break with a bad Map polyfill', () => { const realMapSet = Map.prototype.set; @@ -2896,17 +2894,27 @@ describe('ReactIncremental', () => { function Thing() { throw new Error('No.'); } + // This class uses legacy context, which triggers warnings, + // the procedures for which use a Map to store fibers. class Boundary extends React.Component { state = {didError: false}; componentDidCatch() { this.setState({didError: true}); } + static contextTypes = { + color: () => null, + }; render() { return this.state.didError ? null : ; } } ReactNoop.render(); - expect(Scheduler).toFlushWithoutYielding(); + expect(() => { + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + ['Legacy context API has been detected within a strict-mode tree'], + {withoutStack: true}, + ); } // First, verify that this code path normally receives Fibers as keys, @@ -2952,8 +2960,11 @@ describe('ReactIncremental', () => { }; React = require('react'); ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); try { triggerCodePathThatUsesFibersAsMapKeys(); + } catch (e) { + throw e; } finally { // eslint-disable-next-line no-extend-native Map.prototype.set = realMapSet; diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index fb01a550a114a..b6b957647e044 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -316,10 +316,8 @@ describe('ReactDebugFiberPerf', () => { addComment('Should not print a warning'); expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev( [ - 'componentWillMount: Please update the following components ' + - 'to use componentDidMount instead: NotCascading' + - '\n\ncomponentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: NotCascading', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', ], {withoutStack: true}, ); @@ -358,14 +356,10 @@ describe('ReactDebugFiberPerf', () => { addComment('Mount'); expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev( [ - 'componentWillMount: Please update the following components ' + - 'to use componentDidMount instead: AllLifecycles' + - '\n\ncomponentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: AllLifecycles' + - '\n\ncomponentWillUpdate: Please update the following components ' + - 'to use componentDidUpdate instead: AllLifecycles', - 'Legacy context API has been detected within a strict-mode tree: \n\n' + - 'Please update the following components: AllLifecycles', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', + 'Using UNSAFE_componentWillUpdate in strict mode is not recommended', + 'Legacy context API has been detected within a strict-mode tree', ], {withoutStack: true}, ); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js index e161942a2986a..cf57910c7a530 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js @@ -76,8 +76,7 @@ describe('ReactIncrementalReflection', () => { expect(() => expect(Scheduler).toFlushAndYield(['componentDidMount: true']), ).toWarnDev( - 'componentWillMount: Please update the following components ' + - 'to use componentDidMount instead: Component', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', {withoutStack: true}, ); @@ -116,8 +115,7 @@ describe('ReactIncrementalReflection', () => { ReactNoop.render(); expect(() => expect(Scheduler).toFlushAndYield(['Component'])).toWarnDev( - 'componentWillMount: Please update the following components ' + - 'to use componentDidMount instead: Component', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', {withoutStack: true}, ); @@ -222,10 +220,10 @@ describe('ReactIncrementalReflection', () => { expect(() => expect(Scheduler).toFlushAndYield([['componentDidMount', span()]]), ).toWarnDev( - 'componentWillMount: Please update the following components ' + - 'to use componentDidMount instead: Component' + - '\n\ncomponentWillUpdate: Please update the following components ' + - 'to use componentDidUpdate instead: Component', + [ + 'Using UNSAFE_componentWillMount in strict mode is not recommended', + 'Using UNSAFE_componentWillUpdate in strict mode is not recommended', + ], {withoutStack: true}, ); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js index 58dac988901aa..ccb71f60dfbe2 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js @@ -346,8 +346,7 @@ describe('ReactIncrementalUpdates', () => { } ReactNoop.render(); expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev( - 'componentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: Foo', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', {withoutStack: true}, ); diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js index d64781dc6bcca..f1401069ccb3e 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.internal.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -349,69 +349,10 @@ describe('ReactStrictMode', () => { }); root.update(); expect(() => Scheduler.unstable_flushAll()).toWarnDev( - 'Unsafe lifecycle methods were found within a strict-mode tree:' + - '\n\ncomponentWillMount: Please update the following components ' + - 'to use componentDidMount instead: AsyncRoot' + - '\n\ncomponentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: Bar, Foo' + - '\n\ncomponentWillUpdate: Please update the following components ' + - 'to use componentDidUpdate instead: AsyncRoot' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', - {withoutStack: true}, - ); - - // Dedupe - root.update(); - Scheduler.unstable_flushAll(); - }); - - it('should coalesce warnings by lifecycle name', () => { - class AsyncRoot extends React.Component { - UNSAFE_componentWillMount() {} - UNSAFE_componentWillUpdate() {} - render() { - return ; - } - } - class Parent extends React.Component { - componentWillMount() {} - componentWillUpdate() {} - componentWillReceiveProps() {} - render() { - return ; - } - } - class Child extends React.Component { - UNSAFE_componentWillReceiveProps() {} - render() { - return null; - } - } - - const root = ReactTestRenderer.create(null, { - unstable_isConcurrent: true, - }); - root.update(); - - expect(() => { - expect(() => Scheduler.unstable_flushAll()).toWarnDev( - 'Unsafe lifecycle methods were found within a strict-mode tree:' + - '\n\ncomponentWillMount: Please update the following components ' + - 'to use componentDidMount instead: AsyncRoot, Parent' + - '\n\ncomponentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: Child, Parent' + - '\n\ncomponentWillUpdate: Please update the following components ' + - 'to use componentDidUpdate instead: AsyncRoot, Parent' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', - {withoutStack: true}, - ); - }).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', + 'Using UNSAFE_componentWillUpdate in strict mode is not recommended', ], {withoutStack: true}, ); @@ -445,21 +386,13 @@ describe('ReactStrictMode', () => { }); root.update(); expect(() => Scheduler.unstable_flushAll()).toWarnDev( - 'Unsafe lifecycle methods were found within a strict-mode tree:' + - '\n\ncomponentWillMount: Please update the following components ' + - 'to use componentDidMount instead: Foo' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', {withoutStack: true}, ); root.update(); expect(() => Scheduler.unstable_flushAll()).toWarnDev( - 'Unsafe lifecycle methods were found within a strict-mode tree:' + - '\n\ncomponentWillMount: Please update the following components ' + - 'to use componentDidMount instead: Bar' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', + 'Using UNSAFE_componentWillMount in strict mode is not recommended', {withoutStack: true}, ); @@ -507,13 +440,8 @@ describe('ReactStrictMode', () => { } expect(() => ReactTestRenderer.create()).toWarnDev( - 'Unsafe lifecycle methods were found within a strict-mode tree:' + - '\n in StrictMode (at **)' + - '\n in SyncRoot (at **)' + - '\n\ncomponentWillReceiveProps: Please update the following components ' + - 'to use static getDerivedStateFromProps instead: Bar, Foo' + - '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-strict-mode-warnings', + 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended', + {withoutStack: true}, ); // Dedupe diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.internal.js b/packages/react/src/__tests__/createReactClassIntegration-test.internal.js index b1bd7c9b1c913..f6b7a955d5134 100644 --- a/packages/react/src/__tests__/createReactClassIntegration-test.internal.js +++ b/packages/react/src/__tests__/createReactClassIntegration-test.internal.js @@ -51,10 +51,7 @@ describe('create-react-class-integration', () => { }); expect(() => ReactNative.render(, 1)).toLowPriorityWarnDev( - '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: MyNativeComponent', + 'componentWillMount has been renamed', {withoutStack: true}, ); }); @@ -68,9 +65,7 @@ describe('create-react-class-integration', () => { }); expect(() => ReactNative.render(, 1)).toLowPriorityWarnDev( - 'componentWillReceiveProps is deprecated and will be removed in the next major version. ' + - 'Use static getDerivedStateFromProps instead.' + - '\n\nPlease update the following components: MyNativeComponent', + 'componentWillReceiveProps has been renamed', {withoutStack: true}, ); }); diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js index 36b6bb047732b..542a35087033f 100644 --- a/packages/react/src/__tests__/createReactClassIntegration-test.js +++ b/packages/react/src/__tests__/createReactClassIntegration-test.js @@ -560,9 +560,9 @@ describe('create-react-class-integration', () => { ); }).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); @@ -604,9 +604,9 @@ describe('create-react-class-integration', () => { ); }).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); @@ -649,9 +649,9 @@ describe('create-react-class-integration', () => { ReactDOM.render(, div), ).toLowPriorityWarnDev( [ - 'componentWillMount is deprecated', - 'componentWillReceiveProps is deprecated', - 'componentWillUpdate is deprecated', + 'componentWillMount has been renamed', + 'componentWillReceiveProps has been renamed', + 'componentWillUpdate has been renamed', ], {withoutStack: true}, ); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 1e1ec36101841..f71eb8275ed9b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -15,7 +15,7 @@ import typeof * as PersistentFeatureFlagsType from './ReactFeatureFlags.persiste export const debugRenderPhaseSideEffects = false; export const debugRenderPhaseSideEffectsForStrictMode = false; export const enableUserTimingAPI = __DEV__; -export const warnAboutDeprecatedLifecycles = false; +export const warnAboutDeprecatedLifecycles = true; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = false; export const enableSchedulerTracing = false; diff --git a/scripts/print-warnings/print-warnings.js b/scripts/print-warnings/print-warnings.js index db7630e9787fb..cba05817d3e82 100644 --- a/scripts/print-warnings/print-warnings.js +++ b/scripts/print-warnings/print-warnings.js @@ -52,8 +52,17 @@ function transform(file, enc, cb) { // warning messages can be concatenated (`+`) at runtime, so here's // a trivial partial evaluator that interprets the literal value - const warningMsgLiteral = evalToString(node.arguments[1]); - warnings.add(JSON.stringify(warningMsgLiteral)); + try { + const warningMsgLiteral = evalToString(node.arguments[1]); + warnings.add(JSON.stringify(warningMsgLiteral)); + } catch (error) { + console.error( + 'Failed to extract warning message from', + file.path + ); + console.error(astPath.node.loc); + throw error; + } } }, },