diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js index 7ac51d154acb9..e8aec9ddbacbc 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js @@ -38,6 +38,19 @@ function initModules() { const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); +function formatValue(val) { + if (val === null) { + return 'null'; + } + if (val === undefined) { + return 'undefined'; + } + if (typeof val === 'string') { + return val; + } + return JSON.stringify(val); +} + describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { beforeEach(() => { resetModules(); @@ -72,17 +85,17 @@ describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { lifecycleContextLog.push(nextContext); } render() { - return typeof this.context; + return formatValue(this.context); } } function LegacyFnConsumer(props, context) { - return typeof context; + return formatValue(context); } LegacyFnConsumer.contextTypes = {foo() {}}; function RegularFn(props, context) { - return typeof context; + return formatValue(context); } const e = await render( @@ -95,7 +108,7 @@ describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { , 3, ); - expect(e.textContent).toBe('undefinedundefinedundefined'); + expect(e.textContent).toBe('{}undefinedundefined'); expect(lifecycleContextLog).toEqual([]); }); @@ -114,7 +127,7 @@ describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { class RenderPropConsumer extends React.Component { render() { - return {value => value}; + return {value => formatValue(value)}; } } @@ -132,12 +145,12 @@ describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { lifecycleContextLog.push(nextContext); } render() { - return this.context; + return formatValue(this.context); } } function FnConsumer() { - return React.useContext(Ctx); + return formatValue(React.useContext(Ctx)); } const e = await render( diff --git a/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js index 808380e5eb43c..f788748aa909d 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js @@ -23,6 +23,19 @@ describe('ReactLegacyContextDisabled', () => { ReactFeatureFlags.disableLegacyContext = true; }); + function formatValue(val) { + if (val === null) { + return 'null'; + } + if (val === undefined) { + return 'undefined'; + } + if (typeof val === 'string') { + return val; + } + return JSON.stringify(val); + } + it('warns for legacy context', () => { class LegacyProvider extends React.Component { static childContextTypes = { @@ -52,17 +65,17 @@ describe('ReactLegacyContextDisabled', () => { lifecycleContextLog.push(nextContext); } render() { - return typeof this.context; + return formatValue(this.context); } } function LegacyFnConsumer(props, context) { - return typeof context; + return formatValue(context); } LegacyFnConsumer.contextTypes = {foo() {}}; function RegularFn(props, context) { - return typeof context; + return formatValue(context); } const container = document.createElement('div'); @@ -88,7 +101,7 @@ describe('ReactLegacyContextDisabled', () => { ], {withoutStack: true}, ); - expect(container.textContent).toBe('undefinedundefinedundefined'); + expect(container.textContent).toBe('{}undefinedundefined'); expect(lifecycleContextLog).toEqual([]); // Test update path. @@ -102,8 +115,8 @@ describe('ReactLegacyContextDisabled', () => { , container, ); - expect(container.textContent).toBe('undefinedundefinedundefined'); - expect(lifecycleContextLog).toEqual([undefined, undefined, undefined]); + expect(container.textContent).toBe('{}undefinedundefined'); + expect(lifecycleContextLog).toEqual([{}, {}, {}]); ReactDOM.unmountComponentAtNode(container); }); @@ -122,7 +135,7 @@ describe('ReactLegacyContextDisabled', () => { class RenderPropConsumer extends React.Component { render() { - return {value => value}; + return {value => formatValue(value)}; } } @@ -140,12 +153,12 @@ describe('ReactLegacyContextDisabled', () => { lifecycleContextLog.push(nextContext); } render() { - return this.context; + return formatValue(this.context); } } function FnConsumer() { - return React.useContext(Ctx); + return formatValue(React.useContext(Ctx)); } const container = document.createElement('div'); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 7cbc23ee5b5d4..08c76c72aad7d 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -431,7 +431,8 @@ function resolve( // Extra closure so queue and replace can be captured properly function processChild(element, Component) { - let publicContext = processContext(Component, context, threadID); + const isClass = shouldConstruct(Component); + const publicContext = processContext(Component, context, threadID, isClass); let queue = []; let replace = false; @@ -459,7 +460,7 @@ function resolve( }; let inst; - if (shouldConstruct(Component)) { + if (isClass) { inst = new Component(element.props, publicContext, updater); if (typeof Component.getDerivedStateFromProps === 'function') { diff --git a/packages/react-dom/src/server/ReactPartialRendererContext.js b/packages/react-dom/src/server/ReactPartialRendererContext.js index 04a3c9e6952bf..0caeed21251fb 100644 --- a/packages/react-dom/src/server/ReactPartialRendererContext.js +++ b/packages/react-dom/src/server/ReactPartialRendererContext.js @@ -74,72 +74,89 @@ export function processContext( type: Function, context: Object, threadID: ThreadID, + isClass: boolean, ) { - const contextType = type.contextType; - if (__DEV__) { - if ('contextType' in (type: any)) { - let isValid = - // Allow null for conditional declaration - contextType === null || - (contextType !== undefined && - contextType.$$typeof === REACT_CONTEXT_TYPE && - contextType._context === undefined); // Not a + if (isClass) { + const contextType = type.contextType; + if (__DEV__) { + if ('contextType' in (type: any)) { + let isValid = + // Allow null for conditional declaration + contextType === null || + (contextType !== undefined && + contextType.$$typeof === REACT_CONTEXT_TYPE && + contextType._context === undefined); // Not a - if (!isValid && !didWarnAboutInvalidateContextType.has(type)) { - didWarnAboutInvalidateContextType.add(type); + if (!isValid && !didWarnAboutInvalidateContextType.has(type)) { + didWarnAboutInvalidateContextType.add(type); - let addendum = ''; - if (contextType === undefined) { - addendum = - ' However, it is set to undefined. ' + - 'This can be caused by a typo or by mixing up named and default imports. ' + - 'This can also happen due to a circular dependency, so ' + - 'try moving the createContext() call to a separate file.'; - } else if (typeof contextType !== 'object') { - addendum = ' However, it is set to a ' + typeof contextType + '.'; - } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) { - addendum = ' Did you accidentally pass the Context.Provider instead?'; - } else if (contextType._context !== undefined) { - // - addendum = ' Did you accidentally pass the Context.Consumer instead?'; - } else { - addendum = - ' However, it is set to an object with keys {' + - Object.keys(contextType).join(', ') + - '}.'; + let addendum = ''; + if (contextType === undefined) { + addendum = + ' However, it is set to undefined. ' + + 'This can be caused by a typo or by mixing up named and default imports. ' + + 'This can also happen due to a circular dependency, so ' + + 'try moving the createContext() call to a separate file.'; + } else if (typeof contextType !== 'object') { + addendum = ' However, it is set to a ' + typeof contextType + '.'; + } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) { + addendum = + ' Did you accidentally pass the Context.Provider instead?'; + } else if (contextType._context !== undefined) { + // + addendum = + ' Did you accidentally pass the Context.Consumer instead?'; + } else { + addendum = + ' However, it is set to an object with keys {' + + Object.keys(contextType).join(', ') + + '}.'; + } + warningWithoutStack( + false, + '%s defines an invalid contextType. ' + + 'contextType should point to the Context object returned by React.createContext().%s', + getComponentName(type) || 'Component', + addendum, + ); } - warningWithoutStack( - false, - '%s defines an invalid contextType. ' + - 'contextType should point to the Context object returned by React.createContext().%s', - getComponentName(type) || 'Component', - addendum, - ); } } - } - if (typeof contextType === 'object' && contextType !== null) { - validateContextBounds(contextType, threadID); - return contextType[threadID]; + if (typeof contextType === 'object' && contextType !== null) { + validateContextBounds(contextType, threadID); + return contextType[threadID]; + } + if (disableLegacyContext) { + if (__DEV__) { + if (type.contextTypes) { + warningWithoutStack( + false, + '%s uses the legacy contextTypes API which is no longer supported. ' + + 'Use React.createContext() with static contextType instead.', + getComponentName(type) || 'Unknown', + ); + } + } + return emptyObject; + } else { + const maskedContext = maskContext(type, context); + if (__DEV__) { + if (type.contextTypes) { + checkContextTypes(type.contextTypes, maskedContext, 'context'); + } + } + return maskedContext; + } } else { if (disableLegacyContext) { if (__DEV__) { if (type.contextTypes) { - if (type.prototype && type.prototype.isReactComponent) { - warningWithoutStack( - false, - '%s uses the legacy contextTypes API which is no longer supported. ' + - 'Use React.createContext() with static contextType instead.', - getComponentName(type) || 'Unknown', - ); - } else { - warningWithoutStack( - false, - '%s uses the legacy contextTypes API which is no longer supported. ' + - 'Use React.createContext() with React.useContext() instead.', - getComponentName(type) || 'Unknown', - ); - } + warningWithoutStack( + false, + '%s uses the legacy contextTypes API which is no longer supported. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentName(type) || 'Unknown', + ); } } return undefined; diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 6597b2d630902..7ccc16023eaec 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -555,7 +555,7 @@ function constructClassInstance( ): any { let isLegacyContextConsumer = false; let unmaskedContext = emptyContextObject; - let context; + let context = emptyContextObject; const contextType = ctor.contextType; if (__DEV__) { @@ -719,11 +719,6 @@ function constructClassInstance( // Cache unmasked context so we can avoid recreating masked context unless necessary. // ReactFiberContext usually updates this cache but can't for newly-created instances. if (isLegacyContextConsumer) { - invariant( - context !== undefined, - 'Expected legacy context to always be present. ' + - 'This is likely a bug in React. Please file an issue.', - ); cacheContext(workInProgress, unmaskedContext, context); } @@ -811,7 +806,9 @@ function mountClassInstance( const contextType = ctor.contextType; if (typeof contextType === 'object' && contextType !== null) { instance.context = readContext(contextType); - } else if (!disableLegacyContext) { + } else if (disableLegacyContext) { + instance.context = emptyContextObject; + } else { const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); instance.context = getMaskedContext(workInProgress, unmaskedContext); } @@ -911,7 +908,7 @@ function resumeMountClassInstance( const oldContext = instance.context; const contextType = ctor.contextType; - let nextContext; + let nextContext = emptyContextObject; if (typeof contextType === 'object' && contextType !== null) { nextContext = readContext(contextType); } else if (!disableLegacyContext) { @@ -1060,7 +1057,7 @@ function updateClassInstance( const oldContext = instance.context; const contextType = ctor.contextType; - let nextContext; + let nextContext = emptyContextObject; if (typeof contextType === 'object' && contextType !== null) { nextContext = readContext(contextType); } else if (!disableLegacyContext) { diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index c7e388a1ae6be..158bd244ee0b6 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -338,6 +338,5 @@ "337": "An invalid event responder was provided to host component", "338": "ReactDOMServer does not yet support the fundamental API.", "339": "An invalid value was used as an event responder. Expect one or many event responders created via React.unstable_createResponer().", - "340": "An invalid value was used as an event listener. Expect one or many event listeners created via React.unstable_useResponer().", - "341": "Expected legacy context to always be present. This is likely a bug in React. Please file an issue." + "340": "An invalid value was used as an event listener. Expect one or many event listeners created via React.unstable_useResponer()." }