diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 29af663d90ec..d7421bd15409 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -286,6 +286,7 @@ if (__DEV__) { let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map(); // Tracks components we have already warned about. + const legacyContextCounts = new Map(); const didWarnAboutLegacyContext = new Set(); ReactStrictModeWarnings.recordLegacyContextWarning = ( @@ -300,17 +301,23 @@ if (__DEV__) { ); return; } + const type = fiber.type; - // Dedup strategy: Warn once per component. - if (didWarnAboutLegacyContext.has(fiber.type)) { + // Dedup strategy: Warn once per component + if (didWarnAboutLegacyContext.has(type)) { return; } + // Update legacy context counts + const warningCount = legacyContextCounts.get(type) || 0; + // Increase warning count by 1 + legacyContextCounts.set(type, warningCount + 1); + let warningsForRoot = pendingLegacyContextWarning.get(strictRoot); if ( - fiber.type.contextTypes != null || - fiber.type.childContextTypes != null || + type.contextTypes != null || + type.childContextTypes != null || (instance !== null && typeof instance.getChildContext === 'function') ) { if (warningsForRoot === undefined) { @@ -324,16 +331,30 @@ if (__DEV__) { ReactStrictModeWarnings.flushLegacyContextWarning = () => { ((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach( (fiberArray: FiberArray, strictRoot) => { - const uniqueNames = new Set(); - fiberArray.forEach(fiber => { - uniqueNames.add(getComponentName(fiber.type) || 'Component'); - didWarnAboutLegacyContext.add(fiber.type); - }); - - const sortedNames = setToSortedString(uniqueNames); - const strictRootComponentStack = getStackByFiberInDevAndProd( - strictRoot, - ); + const fibers = fiberArray.length; + let componentNames = ''; + + fiberArray + .sort((a, b) => { + const aCount = legacyContextCounts.get(a.type) || 0; + const bCount = legacyContextCounts.get(b.type) || 0; + return bCount - aCount; + }) + .forEach((fiber, i) => { + const type = fiber.type; + if (didWarnAboutLegacyContext.has(type)) { + return; + } + didWarnAboutLegacyContext.add(type); + // Build up the string of comma separated component names + componentNames += + (getComponentName(fiber.type) || 'Component') + + (i === fibers - 1 ? '' : ', '); + }); + const mostFrequentFiber = fiberArray[0]; + const stack = mostFrequentFiber + ? getStackByFiberInDevAndProd(mostFrequentFiber) + : ''; console.error( 'Legacy context API has been detected within a strict-mode tree.' + @@ -342,8 +363,8 @@ if (__DEV__) { '\n\nPlease update the following components: %s' + '\n\nLearn more about this warning here: https://fb.me/react-legacy-context' + '%s', - sortedNames, - strictRootComponentStack, + componentNames, + stack, ); }, ); diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js index e39b055f79c5..0dcca2ae2f38 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js @@ -1916,8 +1916,7 @@ describe('ReactIncremental', () => { 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Intl, ShowBoth, ShowLocale', - {withoutStack: true}, + 'Please update the following components: Intl, ShowLocale, ShowBoth', ); ReactNoop.render( @@ -1974,7 +1973,6 @@ describe('ReactIncremental', () => { 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: Router, ShowRoute', - {withoutStack: true}, ); }); @@ -2000,14 +1998,11 @@ describe('ReactIncremental', () => { } ReactNoop.render(); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: Recurse', - {withoutStack: true}, ); expect(ops).toEqual([ 'Recurse {}', @@ -2041,20 +2036,17 @@ describe('ReactIncremental', () => { }; ReactNoop.render(); - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( - [ - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Recurse to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Recurse.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - 'Legacy context API has been detected within a strict-mode tree.\n\n' + - 'The old API will be supported in all 16.x releases, but applications ' + - 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Recurse', - ], - {withoutStack: 1}, - ); + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([ + 'Warning: The component appears to be a function component that returns a class instance. ' + + 'Change Recurse to a class that extends React.Component instead. ' + + "If you can't use a class try assigning the prototype on the function as a workaround. " + + '`Recurse.prototype = React.Component.prototype`. ' + + "Don't use an arrow function since it cannot be called with `new` by React.", + 'Legacy context API has been detected within a strict-mode tree.\n\n' + + 'The old API will be supported in all 16.x releases, but applications ' + + 'using it should migrate to the new version.\n\n' + + 'Please update the following components: Recurse', + ]); expect(ops).toEqual([ 'Recurse {}', 'Recurse {"n":2}', @@ -2119,8 +2111,7 @@ describe('ReactIncremental', () => { 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Intl, ShowLocale', - {withoutStack: true}, + 'Please update the following components: ShowLocale, Intl', ); }); @@ -2196,14 +2187,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: Intl, ShowLocaleClass, ShowLocaleFn', - {withoutStack: true}, ); expect(ops).toEqual([ 'Intl:read {}', @@ -2292,14 +2280,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: Intl, ShowLocaleClass, ShowLocaleFn', - {withoutStack: true}, ); expect(ops).toEqual([ 'Intl:read {}', @@ -2365,14 +2350,11 @@ describe('ReactIncremental', () => { // Init ReactNoop.render(); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: Child', - {withoutStack: true}, ); // Trigger an update in the middle of the tree @@ -2419,14 +2401,11 @@ describe('ReactIncremental', () => { // Init ReactNoop.render(); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: ContextProvider', - {withoutStack: true}, ); // Trigger an update in the middle of the tree @@ -2479,7 +2458,7 @@ describe('ReactIncremental', () => { 'using it should migrate to the new version.\n\n' + 'Please update the following components: MyComponent', ], - {withoutStack: true}, + {withoutStack: 1}, ); expect(ops).toEqual([ @@ -2622,14 +2601,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Child, TopContextProvider', - {withoutStack: true}, + 'Please update the following components: TopContextProvider, Child', ); expect(rendered).toEqual(['count:0']); instance.updateCount(); @@ -2688,14 +2664,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Child, MiddleContextProvider, TopContextProvider', - {withoutStack: true}, + 'Please update the following components: TopContextProvider, MiddleContextProvider, Child', ); expect(rendered).toEqual(['count:0']); instance.updateCount(); @@ -2763,14 +2736,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Child, MiddleContextProvider, TopContextProvider', - {withoutStack: true}, + 'Please update the following components: TopContextProvider, MiddleContextProvider, Child', ); expect(rendered).toEqual(['count:0']); instance.updateCount(); @@ -2848,14 +2818,11 @@ describe('ReactIncremental', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + - 'Please update the following components: Child, MiddleContextProvider, TopContextProvider', - {withoutStack: true}, + 'Please update the following components: TopContextProvider, MiddleContextProvider, Child', ); expect(rendered).toEqual(['count:0, name:brian']); topInstance.updateCount(); @@ -2956,10 +2923,9 @@ describe('ReactIncremental', () => { ReactNoop.render(); expect(() => { expect(Scheduler).toFlushWithoutYielding(); - }).toErrorDev( - ['Legacy context API has been detected within a strict-mode tree'], - {withoutStack: true}, - ); + }).toErrorDev([ + 'Legacy context API has been detected within a strict-mode tree', + ]); } // First, verify that this code path normally receives Fibers as keys, diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 519c2d3f3a85..493cd79cc8bd 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1148,14 +1148,11 @@ describe('ReactIncrementalErrorHandling', () => { , ); - expect(() => - expect(Scheduler).toFlushWithoutYielding(), - ).toErrorDev( + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev( 'Legacy context API has been detected within a strict-mode tree.\n\n' + 'The old API will be supported in all 16.x releases, but ' + 'applications using it should migrate to the new version.\n\n' + - 'Please update the following components: Connector, Provider', - {withoutStack: true}, + 'Please update the following components: Provider, Connector', ); // If the context stack does not unwind, span will get 'abcde' @@ -1649,19 +1646,16 @@ describe('ReactIncrementalErrorHandling', () => { ReactNoop.render(); expect(() => { expect(Scheduler).toFlushAndThrow('Oops!'); - }).toErrorDev( - [ - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Provider to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Provider.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - 'Legacy context API has been detected within a strict-mode tree.\n\n' + - 'The old API will be supported in all 16.x releases, but ' + - 'applications using it should migrate to the new version.\n\n' + - 'Please update the following components: Provider', - ], - {withoutStack: 1}, - ); + }).toErrorDev([ + 'Warning: The component appears to be a function component that returns a class instance. ' + + 'Change Provider to a class that extends React.Component instead. ' + + "If you can't use a class try assigning the prototype on the function as a workaround. " + + '`Provider.prototype = React.Component.prototype`. ' + + "Don't use an arrow function since it cannot be called with `new` by React.", + 'Legacy context API has been detected within a strict-mode tree.\n\n' + + 'The old API will be supported in all 16.x releases, but ' + + 'applications using it should migrate to the new version.\n\n' + + 'Please update the following components: Provider', + ]); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index a88cd8e3a89f..08c569c23950 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -371,7 +371,7 @@ describe('ReactDebugFiberPerf', () => { 'Using UNSAFE_componentWillUpdate in strict mode is not recommended', 'Legacy context API has been detected within a strict-mode tree', ], - {withoutStack: true}, + {withoutStack: 3}, ); ReactNoop.render(); addComment('Update'); diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js index 03c5ad4eec66..34d854f78cdd 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js @@ -1198,7 +1198,6 @@ describe('ReactNewContext', () => { 'The old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.\n\n' + 'Please update the following components: LegacyProvider', - {withoutStack: true}, ); expect(ReactNoop.getChildren()).toEqual([span('Child')]); diff --git a/packages/react/src/__tests__/ReactStrictMode-test.js b/packages/react/src/__tests__/ReactStrictMode-test.js index 15b2ce719c76..87b153ef61e6 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.js @@ -875,9 +875,10 @@ describe('context legacy', () => { '\n\nThe old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.' + '\n\nPlease update the following components: ' + - 'FunctionalLegacyContextConsumer, LegacyContextConsumer, LegacyContextProvider' + + 'LegacyContextProvider, LegacyContextConsumer, FunctionalLegacyContextConsumer' + '\n\nLearn more about this warning here: ' + 'https://fb.me/react-legacy-context' + + '\n in LegacyContextProvider (at **)' + '\n in StrictMode (at **)' + '\n in div (at **)' + '\n in Root (at **)',