diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index af1f409aa2..56219535e4 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -668,6 +668,11 @@ function mountClassInstance( workInProgress, instance, ); + + ReactStrictModeWarnings.recordLegacyContextWarning( + workInProgress, + instance, + ); } if (warnAboutDeprecatedLifecycles) { diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index da32034b08..87f97dcc61 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -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; diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 8887bec3c9..92fb1f1b13 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -21,6 +21,8 @@ type LIFECYCLE = | 'UNSAFE_componentWillUpdate'; type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array}; type FiberToLifecycleMap = Map; +type FiberArray = Array; +type FiberToFiberComponentsMap = Map; 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 = []; let pendingComponentWillUpdateWarnings: Array = []; 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; diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js index 9236ae7754..56b5e9e530 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.internal.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -12,6 +12,7 @@ let React; let ReactFeatureFlags; let ReactTestRenderer; +let PropTypes; describe('ReactStrictMode', () => { describe('debugRenderPhaseSideEffects', () => { @@ -805,4 +806,72 @@ describe('ReactStrictMode', () => { renderer.update(); }); }); + + 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 ; + } + } + + LegacyContextProvider.childContextTypes = { + color: PropTypes.string, + }; + + class LegacyContextConsumer extends React.Component { + render() { + return null; + } + } + + const {StrictMode} = React; + + class Root extends React.Component { + render() { + return ( +
+ + + +
+ ); + } + } + + LegacyContextConsumer.contextTypes = { + color: PropTypes.string, + }; + + let rendered; + + expect(() => { + rendered = ReactTestRenderer.create(); + }).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(); + rendered.update(); + }); + }); }); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c4618b5528..91ebce8c09 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -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__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js index cdfe7dd3d6..5fc79cc55d 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js index ece21aa11f..f80827ef02 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 874d62ae11..086518d9f3 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -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() { diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 6b435c6bdf..0eb9c58b4e 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 6d8f9f3c42..06c1014dd7 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index dc7823de21..b8e0cbcea1 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index ddc82b3db0..0923924957 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -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 diff --git a/scripts/rollup/shims/react-native-fb/ReactFeatureFlags.js b/scripts/rollup/shims/react-native-fb/ReactFeatureFlags.js index ea5c5ba0f1..4f639fac17 100644 --- a/scripts/rollup/shims/react-native-fb/ReactFeatureFlags.js +++ b/scripts/rollup/shims/react-native-fb/ReactFeatureFlags.js @@ -14,6 +14,7 @@ const ReactFeatureFlags = { debugRenderPhaseSideEffects: false, debugRenderPhaseSideEffectsForStrictMode: false, warnAboutDeprecatedLifecycles: true, + warnAboutLegacyContextAPI: true, }; module.exports = ReactFeatureFlags;