@samitouri / QOS-React / commits / 7350358374

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;