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
This commit is contained in:
Chang Yan
2018-05-22 15:38:02 -07:00
committed by GitHub
parent e885791842
commit 7350358374
13 changed files with 158 additions and 0 deletions
@@ -668,6 +668,11 @@ function mountClassInstance(
workInProgress,
instance,
);
ReactStrictModeWarnings.recordLegacyContextWarning(
workInProgress,
instance,
);
}
if (warnAboutDeprecatedLifecycles) {
+5
View File
@@ -42,6 +42,7 @@ import {
enableUserTimingAPI,
replayFailedUnitOfWorkWithInvokeGuardedCallback,
warnAboutDeprecatedLifecycles,
warnAboutLegacyContextAPI,
} from 'shared/ReactFeatureFlags';
import getComponentName from 'shared/getComponentName';
import invariant from 'fbjs/lib/invariant';
@@ -440,6 +441,10 @@ function commitAllLifeCycles(
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
}
if (warnAboutLegacyContextAPI) {
ReactStrictModeWarnings.flushLegacyContextWarning();
}
}
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
@@ -21,6 +21,8 @@ type LIFECYCLE =
| 'UNSAFE_componentWillUpdate';
type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array<Fiber>};
type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
type FiberArray = Array<Fiber>;
type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
const ReactStrictModeWarnings = {
discardPendingWarnings(): void {},
@@ -28,6 +30,8 @@ const ReactStrictModeWarnings = {
flushPendingUnsafeLifecycleWarnings(): void {},
recordDeprecationWarnings(fiber: Fiber, instance: any): void {},
recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {},
recordLegacyContextWarning(fiber: Fiber, instance: any): void {},
flushLegacyContextWarning(): void {},
};
if (__DEV__) {
@@ -41,10 +45,12 @@ if (__DEV__) {
let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map();
let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
// Tracks components we have already warned about.
const didWarnAboutDeprecatedLifecycles = new Set();
const didWarnAboutUnsafeLifecycles = new Set();
const didWarnAboutLegacyContext = new Set();
const setToSortedString = set => {
const array = [];
@@ -59,6 +65,7 @@ if (__DEV__) {
pendingComponentWillReceivePropsWarnings = [];
pendingComponentWillUpdateWarnings = [];
pendingUnsafeLifecycleWarnings = new Map();
pendingLegacyContextWarning = new Map();
};
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
@@ -289,6 +296,67 @@ if (__DEV__) {
});
}
};
ReactStrictModeWarnings.recordLegacyContextWarning = (
fiber: Fiber,
instance: any,
) => {
const strictRoot = findStrictRoot(fiber);
if (strictRoot === null) {
warning(
false,
'Expected to find a StrictMode component in a strict mode tree. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
return;
}
// Dedup strategy: Warn once per component.
if (didWarnAboutLegacyContext.has(fiber.type)) {
return;
}
let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
if (
typeof instance.getChildContext === 'function' ||
fiber.type.contextTypes != null ||
fiber.type.childContextTypes != null
) {
if (warningsForRoot === undefined) {
warningsForRoot = [];
pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
}
warningsForRoot.push(fiber);
}
};
ReactStrictModeWarnings.flushLegacyContextWarning = () => {
((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach(
(fiberArray: FiberArray, strictRoot) => {
const uniqueNames = new Set();
fiberArray.forEach(fiber => {
uniqueNames.add(getComponentName(fiber) || 'Component');
didWarnAboutLegacyContext.add(fiber.type);
});
const sortedNames = setToSortedString(uniqueNames);
const strictRootComponentStack = getStackAddendumByWorkInProgressFiber(
strictRoot,
);
warning(
false,
'Legacy context API has been detected within a strict-mode tree: %s' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here:' +
'\nhttps://fb.me/react-strict-mode-warnings',
strictRootComponentStack,
sortedNames,
);
},
);
};
}
export default ReactStrictModeWarnings;
@@ -12,6 +12,7 @@
let React;
let ReactFeatureFlags;
let ReactTestRenderer;
let PropTypes;
describe('ReactStrictMode', () => {
describe('debugRenderPhaseSideEffects', () => {
@@ -805,4 +806,72 @@ describe('ReactStrictMode', () => {
renderer.update(<OuterComponent />);
});
});
describe('context legacy', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactTestRenderer = require('react-test-renderer');
PropTypes = require('prop-types');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.warnAboutLegacyContextAPI = true;
});
it('should warn if the legacy context API have been used in strict mode', () => {
class LegacyContextProvider extends React.Component {
getChildContext() {
return {color: 'purple'};
}
render() {
return <LegacyContextConsumer />;
}
}
LegacyContextProvider.childContextTypes = {
color: PropTypes.string,
};
class LegacyContextConsumer extends React.Component {
render() {
return null;
}
}
const {StrictMode} = React;
class Root extends React.Component {
render() {
return (
<div>
<StrictMode>
<LegacyContextProvider />
</StrictMode>
</div>
);
}
}
LegacyContextConsumer.contextTypes = {
color: PropTypes.string,
};
let rendered;
expect(() => {
rendered = ReactTestRenderer.create(<Root />);
}).toWarnDev(
'Warning: Legacy context API has been detected within a strict-mode tree: ' +
'\n in div (at **)' +
'\n in Root (at **)' +
'\n\nPlease update the following components: LegacyContextConsumer, LegacyContextProvider' +
'\n\nLearn more about this warning here:' +
'\nhttps://fb.me/react-strict-mode-warnings',
);
// Dedupe
rendered = ReactTestRenderer.create(<Root />);
rendered.update(<Root />);
});
});
});
+3
View File
@@ -33,6 +33,9 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
export const warnAboutDeprecatedLifecycles = false;
// Warn about legacy context API
export const warnAboutLegacyContextAPI = false;
// Gather advanced timing metrics for Profiler subtrees.
export const enableProfilerTimer = __DEV__;
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __DEV__;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -26,6 +26,7 @@ export const {
// The rest of the flags are static for better dead code elimination.
export const enableUserTimingAPI = __DEV__;
export const warnAboutLegacyContextAPI = __DEV__;
// Only used in www builds.
export function addUserTimingListener() {
@@ -19,6 +19,7 @@ export const enableSuspense = false;
export const enableUserTimingAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const enableProfilerTimer = __DEV__;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__;
export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -23,6 +23,7 @@ export const {
} = require('ReactFeatureFlags');
// The rest of the flags are static for better dead code elimination.
export const warnAboutLegacyContextAPI = __DEV__;
// In www, we have experimental support for gathering data
// from User Timing API calls in production. By default, we
@@ -14,6 +14,7 @@ const ReactFeatureFlags = {
debugRenderPhaseSideEffects: false,
debugRenderPhaseSideEffectsForStrictMode: false,
warnAboutDeprecatedLifecycles: true,
warnAboutLegacyContextAPI: true,
};
module.exports = ReactFeatureFlags;