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 **)',