Skip to content

Commit 7350358

Browse files
authored
add legacy context API warning in strict mode (#12849)
* add legacy context APIs warning in strict mode * refactor if statement and the warning message * add other flags for type check * add component stack tree and refactor wording * fix the nits
1 parent e885791 commit 7350358

13 files changed

+158
-0
lines changed

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,11 @@ function mountClassInstance(
668668
workInProgress,
669669
instance,
670670
);
671+
672+
ReactStrictModeWarnings.recordLegacyContextWarning(
673+
workInProgress,
674+
instance,
675+
);
671676
}
672677

673678
if (warnAboutDeprecatedLifecycles) {

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
enableUserTimingAPI,
4343
replayFailedUnitOfWorkWithInvokeGuardedCallback,
4444
warnAboutDeprecatedLifecycles,
45+
warnAboutLegacyContextAPI,
4546
} from 'shared/ReactFeatureFlags';
4647
import getComponentName from 'shared/getComponentName';
4748
import invariant from 'fbjs/lib/invariant';
@@ -440,6 +441,10 @@ function commitAllLifeCycles(
440441
if (warnAboutDeprecatedLifecycles) {
441442
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
442443
}
444+
445+
if (warnAboutLegacyContextAPI) {
446+
ReactStrictModeWarnings.flushLegacyContextWarning();
447+
}
443448
}
444449
while (nextEffect !== null) {
445450
const effectTag = nextEffect.effectTag;

packages/react-reconciler/src/ReactStrictModeWarnings.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ type LIFECYCLE =
2121
| 'UNSAFE_componentWillUpdate';
2222
type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array<Fiber>};
2323
type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
24+
type FiberArray = Array<Fiber>;
25+
type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
2426

2527
const ReactStrictModeWarnings = {
2628
discardPendingWarnings(): void {},
2729
flushPendingDeprecationWarnings(): void {},
2830
flushPendingUnsafeLifecycleWarnings(): void {},
2931
recordDeprecationWarnings(fiber: Fiber, instance: any): void {},
3032
recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {},
33+
recordLegacyContextWarning(fiber: Fiber, instance: any): void {},
34+
flushLegacyContextWarning(): void {},
3135
};
3236

3337
if (__DEV__) {
@@ -41,10 +45,12 @@ if (__DEV__) {
4145
let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
4246
let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
4347
let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map();
48+
let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
4449

4550
// Tracks components we have already warned about.
4651
const didWarnAboutDeprecatedLifecycles = new Set();
4752
const didWarnAboutUnsafeLifecycles = new Set();
53+
const didWarnAboutLegacyContext = new Set();
4854

4955
const setToSortedString = set => {
5056
const array = [];
@@ -59,6 +65,7 @@ if (__DEV__) {
5965
pendingComponentWillReceivePropsWarnings = [];
6066
pendingComponentWillUpdateWarnings = [];
6167
pendingUnsafeLifecycleWarnings = new Map();
68+
pendingLegacyContextWarning = new Map();
6269
};
6370

6471
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
@@ -289,6 +296,67 @@ if (__DEV__) {
289296
});
290297
}
291298
};
299+
300+
ReactStrictModeWarnings.recordLegacyContextWarning = (
301+
fiber: Fiber,
302+
instance: any,
303+
) => {
304+
const strictRoot = findStrictRoot(fiber);
305+
if (strictRoot === null) {
306+
warning(
307+
false,
308+
'Expected to find a StrictMode component in a strict mode tree. ' +
309+
'This error is likely caused by a bug in React. Please file an issue.',
310+
);
311+
return;
312+
}
313+
314+
// Dedup strategy: Warn once per component.
315+
if (didWarnAboutLegacyContext.has(fiber.type)) {
316+
return;
317+
}
318+
319+
let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
320+
321+
if (
322+
typeof instance.getChildContext === 'function' ||
323+
fiber.type.contextTypes != null ||
324+
fiber.type.childContextTypes != null
325+
) {
326+
if (warningsForRoot === undefined) {
327+
warningsForRoot = [];
328+
pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
329+
}
330+
warningsForRoot.push(fiber);
331+
}
332+
};
333+
334+
ReactStrictModeWarnings.flushLegacyContextWarning = () => {
335+
((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach(
336+
(fiberArray: FiberArray, strictRoot) => {
337+
const uniqueNames = new Set();
338+
fiberArray.forEach(fiber => {
339+
uniqueNames.add(getComponentName(fiber) || 'Component');
340+
didWarnAboutLegacyContext.add(fiber.type);
341+
});
342+
343+
const sortedNames = setToSortedString(uniqueNames);
344+
const strictRootComponentStack = getStackAddendumByWorkInProgressFiber(
345+
strictRoot,
346+
);
347+
348+
warning(
349+
false,
350+
'Legacy context API has been detected within a strict-mode tree: %s' +
351+
'\n\nPlease update the following components: %s' +
352+
'\n\nLearn more about this warning here:' +
353+
'\nhttps://fb.me/react-strict-mode-warnings',
354+
strictRootComponentStack,
355+
sortedNames,
356+
);
357+
},
358+
);
359+
};
292360
}
293361

294362
export default ReactStrictModeWarnings;

packages/react/src/__tests__/ReactStrictMode-test.internal.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
let React;
1313
let ReactFeatureFlags;
1414
let ReactTestRenderer;
15+
let PropTypes;
1516

1617
describe('ReactStrictMode', () => {
1718
describe('debugRenderPhaseSideEffects', () => {
@@ -805,4 +806,72 @@ describe('ReactStrictMode', () => {
805806
renderer.update(<OuterComponent />);
806807
});
807808
});
809+
810+
describe('context legacy', () => {
811+
beforeEach(() => {
812+
jest.resetModules();
813+
React = require('react');
814+
ReactTestRenderer = require('react-test-renderer');
815+
PropTypes = require('prop-types');
816+
ReactFeatureFlags = require('shared/ReactFeatureFlags');
817+
ReactFeatureFlags.warnAboutLegacyContextAPI = true;
818+
});
819+
820+
it('should warn if the legacy context API have been used in strict mode', () => {
821+
class LegacyContextProvider extends React.Component {
822+
getChildContext() {
823+
return {color: 'purple'};
824+
}
825+
826+
render() {
827+
return <LegacyContextConsumer />;
828+
}
829+
}
830+
831+
LegacyContextProvider.childContextTypes = {
832+
color: PropTypes.string,
833+
};
834+
835+
class LegacyContextConsumer extends React.Component {
836+
render() {
837+
return null;
838+
}
839+
}
840+
841+
const {StrictMode} = React;
842+
843+
class Root extends React.Component {
844+
render() {
845+
return (
846+
<div>
847+
<StrictMode>
848+
<LegacyContextProvider />
849+
</StrictMode>
850+
</div>
851+
);
852+
}
853+
}
854+
855+
LegacyContextConsumer.contextTypes = {
856+
color: PropTypes.string,
857+
};
858+
859+
let rendered;
860+
861+
expect(() => {
862+
rendered = ReactTestRenderer.create(<Root />);
863+
}).toWarnDev(
864+
'Warning: Legacy context API has been detected within a strict-mode tree: ' +
865+
'\n in div (at **)' +
866+
'\n in Root (at **)' +
867+
'\n\nPlease update the following components: LegacyContextConsumer, LegacyContextProvider' +
868+
'\n\nLearn more about this warning here:' +
869+
'\nhttps://fb.me/react-strict-mode-warnings',
870+
);
871+
872+
// Dedupe
873+
rendered = ReactTestRenderer.create(<Root />);
874+
rendered.update(<Root />);
875+
});
876+
});
808877
});

packages/shared/ReactFeatureFlags.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
3333
// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
3434
export const warnAboutDeprecatedLifecycles = false;
3535

36+
// Warn about legacy context API
37+
export const warnAboutLegacyContextAPI = false;
38+
3639
// Gather advanced timing metrics for Profiler subtrees.
3740
export const enableProfilerTimer = __DEV__;
3841

packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
1818
export const enableGetDerivedStateFromCatch = false;
1919
export const enableSuspense = false;
2020
export const warnAboutDeprecatedLifecycles = false;
21+
export const warnAboutLegacyContextAPI = __DEV__;
2122
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
2223
export const enableProfilerTimer = __DEV__;
2324
export const fireGetDerivedStateFromPropsOnStateUpdates = true;

packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
1818
export const enableGetDerivedStateFromCatch = false;
1919
export const enableSuspense = false;
2020
export const warnAboutDeprecatedLifecycles = false;
21+
export const warnAboutLegacyContextAPI = false;
2122
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
2223
export const enableProfilerTimer = false;
2324
export const fireGetDerivedStateFromPropsOnStateUpdates = true;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const {
2626

2727
// The rest of the flags are static for better dead code elimination.
2828
export const enableUserTimingAPI = __DEV__;
29+
export const warnAboutLegacyContextAPI = __DEV__;
2930

3031
// Only used in www builds.
3132
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const enableSuspense = false;
1919
export const enableUserTimingAPI = __DEV__;
2020
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
2121
export const warnAboutDeprecatedLifecycles = false;
22+
export const warnAboutLegacyContextAPI = false;
2223
export const enableProfilerTimer = __DEV__;
2324
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
2425

packages/shared/forks/ReactFeatureFlags.persistent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
1818
export const enableGetDerivedStateFromCatch = false;
1919
export const enableSuspense = false;
2020
export const warnAboutDeprecatedLifecycles = false;
21+
export const warnAboutLegacyContextAPI = false;
2122
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
2223
export const enableProfilerTimer = false;
2324
export const fireGetDerivedStateFromPropsOnStateUpdates = true;

0 commit comments

Comments
 (0)