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
Chang Yan committed
May 22, 2018 at 15:38 UTC
7350358374036c0834ea6b69cea7491abbbb1af0
13 files changed
+158
packages/react-reconciler/src/ReactFiberClassComponent.js
+5
@@ -668,6 +668,11 @@ function mountClassInstance(
668
workInProgress,
669
instance,
670
);
671
+
672
+ ReactStrictModeWarnings.recordLegacyContextWarning(
673
+ workInProgress,
674
+ instance,
675
+ );
676
}
677
678
if (warnAboutDeprecatedLifecycles) {
packages/react-reconciler/src/ReactFiberScheduler.js
+5
@@ -42,6 +42,7 @@ import {
42
enableUserTimingAPI,
43
replayFailedUnitOfWorkWithInvokeGuardedCallback,
44
warnAboutDeprecatedLifecycles,
45
+ warnAboutLegacyContextAPI,
46
} from 'shared/ReactFeatureFlags';
47
import getComponentName from 'shared/getComponentName';
48
import invariant from 'fbjs/lib/invariant';
@@ -440,6 +441,10 @@ function commitAllLifeCycles(
441
if (warnAboutDeprecatedLifecycles) {
442
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
443
}
444
+
445
+ if (warnAboutLegacyContextAPI) {
446
+ ReactStrictModeWarnings.flushLegacyContextWarning();
447
+ }
448
}
449
while (nextEffect !== null) {
450
const effectTag = nextEffect.effectTag;
packages/react-reconciler/src/ReactStrictModeWarnings.js
+68
@@ -21,6 +21,8 @@ type LIFECYCLE =
21
| 'UNSAFE_componentWillUpdate';
22
type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array<Fiber>};
23
type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
24
+type FiberArray = Array<Fiber>;
25
+type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
26
27
const ReactStrictModeWarnings = {
28
discardPendingWarnings(): void {},
@@ -28,6 +30,8 @@ const ReactStrictModeWarnings = {
30
flushPendingUnsafeLifecycleWarnings(): void {},
31
recordDeprecationWarnings(fiber: Fiber, instance: any): void {},
32
recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {},
33
+ recordLegacyContextWarning(fiber: Fiber, instance: any): void {},
34
+ flushLegacyContextWarning(): void {},
35
};
36
37
if (__DEV__) {
@@ -41,10 +45,12 @@ if (__DEV__) {
45
let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
46
let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
47
let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map();
48
+ let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
49
50
// Tracks components we have already warned about.
51
const didWarnAboutDeprecatedLifecycles = new Set();
52
const didWarnAboutUnsafeLifecycles = new Set();
53
+ const didWarnAboutLegacyContext = new Set();
54
55
const setToSortedString = set => {
56
const array = [];
@@ -59,6 +65,7 @@ if (__DEV__) {
65
pendingComponentWillReceivePropsWarnings = [];
66
pendingComponentWillUpdateWarnings = [];
67
pendingUnsafeLifecycleWarnings = new Map();
68
+ pendingLegacyContextWarning = new Map();
69
};
70
71
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
@@ -289,6 +296,67 @@ if (__DEV__) {
296
});
297
}
298
};
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
+ };
360
}
361
362
export default ReactStrictModeWarnings;
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+69
@@ -12,6 +12,7 @@
12
let React;
13
let ReactFeatureFlags;
14
let ReactTestRenderer;
15
+let PropTypes;
16
17
describe('ReactStrictMode', () => {
18
describe('debugRenderPhaseSideEffects', () => {
@@ -805,4 +806,72 @@ describe('ReactStrictMode', () => {
806
renderer.update(<OuterComponent />);
807
});
808
});
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
+ });
877
});
packages/shared/ReactFeatureFlags.js
+3
@@ -33,6 +33,9 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
33
// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
34
export const warnAboutDeprecatedLifecycles = false;
35
36
+// Warn about legacy context API
37
+export const warnAboutLegacyContextAPI = false;
38
+
39
// Gather advanced timing metrics for Profiler subtrees.
40
export const enableProfilerTimer = __DEV__;
41
packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
+1
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
18
export const enableGetDerivedStateFromCatch = false;
19
export const enableSuspense = false;
20
export const warnAboutDeprecatedLifecycles = false;
21
+export const warnAboutLegacyContextAPI = __DEV__;
22
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
23
export const enableProfilerTimer = __DEV__;
24
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
+1
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
18
export const enableGetDerivedStateFromCatch = false;
19
export const enableSuspense = false;
20
export const warnAboutDeprecatedLifecycles = false;
21
+export const warnAboutLegacyContextAPI = false;
22
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
23
export const enableProfilerTimer = false;
24
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -26,6 +26,7 @@ export const {
26
27
// The rest of the flags are static for better dead code elimination.
28
export const enableUserTimingAPI = __DEV__;
29
+export const warnAboutLegacyContextAPI = __DEV__;
30
31
// Only used in www builds.
32
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -19,6 +19,7 @@ export const enableSuspense = false;
19
export const enableUserTimingAPI = __DEV__;
20
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
21
export const warnAboutDeprecatedLifecycles = false;
22
+export const warnAboutLegacyContextAPI = false;
23
export const enableProfilerTimer = __DEV__;
24
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
25
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
18
export const enableGetDerivedStateFromCatch = false;
19
export const enableSuspense = false;
20
export const warnAboutDeprecatedLifecycles = false;
21
+export const warnAboutLegacyContextAPI = false;
22
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
23
export const enableProfilerTimer = false;
24
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
18
export const enableGetDerivedStateFromCatch = false;
19
export const enableSuspense = false;
20
export const warnAboutDeprecatedLifecycles = false;
21
+export const warnAboutLegacyContextAPI = false;
22
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
23
export const enableProfilerTimer = false;
24
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -23,6 +23,7 @@ export const {
23
} = require('ReactFeatureFlags');
24
25
// The rest of the flags are static for better dead code elimination.
26
+export const warnAboutLegacyContextAPI = __DEV__;
27
28
// In www, we have experimental support for gathering data
29
// from User Timing API calls in production. By default, we
scripts/rollup/shims/react-native-fb/ReactFeatureFlags.js
+1
@@ -14,6 +14,7 @@ const ReactFeatureFlags = {
14
debugRenderPhaseSideEffects: false,
15
debugRenderPhaseSideEffectsForStrictMode: false,
16
warnAboutDeprecatedLifecycles: true,
17
+ warnAboutLegacyContextAPI: true,
18
};
19
20
module.exports = ReactFeatureFlags;