diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 34654d9c3111..684f830df1bc 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)) { + // Update legacy context counts + const warningCount = legacyContextCounts.get(type) || 0; + // Increase warning count by 1 + legacyContextCounts.set(type, warningCount + 1); + + // Dedup strategy: Warn once per component + if (didWarnAboutLegacyContext.has(type)) { return; } 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,49 +331,41 @@ if (__DEV__) { ReactStrictModeWarnings.flushLegacyContextWarning = () => { ((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach( (fiberArray: FiberArray, strictRoot) => { - const componentStacks = new Map(); - - fiberArray.forEach(fiber => { - const componentName = getComponentName(fiber.type) || 'Component'; - const componentStack = getStackByFiberInDevAndProd(fiber); - let count = 0; - if (componentStacks.has(componentStack)) { - ({count} = (componentStacks.get(componentStack): any)); - } - // Increase count by 1 - componentStacks.set(componentStack, { - count: count + 1, - name: componentName, - stack: componentStack, + 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 ? '' : ', '); }); - didWarnAboutLegacyContext.add(fiber.type); - }); - - // Get the stacks from our componentStacks Map - const stacks = Array.from(componentStacks.values()); - - // Sort the stacks by their counts - stacks.sort((a, b) => b.count - a.count); - - const mostFrequentStack = stacks[0]; - - if (mostFrequentStack) { - // We map to a Set to remove duplicate component names - const componentNames = Array.from( - new Set(stacks.map(stack => stack.name)), - ).join(', '); - - console.error( - 'Legacy context API has been detected within a strict-mode tree.' + - '\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: %s' + - '\n\nLearn more about this warning here: https://fb.me/react-legacy-context' + - '%s', - componentNames, - mostFrequentStack.stack, - ); - } + const mostFrequentFiber = fiberArray[0]; + const stack = mostFrequentFiber + ? getStackByFiberInDevAndProd(mostFrequentFiber) + : ''; + + console.error( + 'Legacy context API has been detected within a strict-mode tree.' + + '\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: %s' + + '\n\nLearn more about this warning here: https://fb.me/react-legacy-context' + + '%s', + 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 1acb4e49c4b8..0dcca2ae2f38 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js @@ -2036,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: 0}, - ); + 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}', @@ -2114,7 +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', + 'Please update the following components: ShowLocale, Intl', ); });