diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js
new file mode 100644
index 0000000000000..19839aa7cd6c2
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js
@@ -0,0 +1,266 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
+
+let React;
+let ReactDOM;
+let ReactDOMServer;
+
+function initModules() {
+ // Reset warning cache.
+ jest.resetModuleRegistry();
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactDOMServer = require('react-dom/server');
+
+ // Make them available to the helpers.
+ return {
+ ReactDOM,
+ ReactDOMServer,
+ };
+}
+
+const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
+
+describe('ReactDOMServerIntegration', () => {
+ beforeEach(() => {
+ resetModules();
+ });
+
+ describe('class contextType', function() {
+ let PurpleContext, RedContext, Context;
+ beforeEach(() => {
+ Context = React.createContext('none');
+
+ class Parent extends React.Component {
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+ }
+ PurpleContext = props => {props.children};
+ RedContext = props => {props.children};
+ });
+
+ itRenders('class child with context', async render => {
+ class ClassChildWithContext extends React.Component {
+ static contextType = Context;
+ render() {
+ const text = this.context;
+ return
{text}
;
+ }
+ }
+
+ const e = await render(
+
+
+ ,
+ );
+ expect(e.textContent).toBe('purple');
+ });
+
+ itRenders('class child without context', async render => {
+ class ClassChildWithoutContext extends React.Component {
+ render() {
+ // this should render blank; context isn't passed to this component.
+ return (
+ {typeof this.context === 'string' ? this.context : ''}
+ );
+ }
+ }
+
+ const e = await render(
+
+
+ ,
+ );
+ expect(e.textContent).toBe('');
+ });
+
+ itRenders('class child with wrong context', async render => {
+ class ClassChildWithWrongContext extends React.Component {
+ static contextType = Context;
+ render() {
+ // this should render blank; context.foo isn't passed to this component.
+ return {this.context.foo}
;
+ }
+ }
+
+ const e = await render(
+
+
+ ,
+ );
+ expect(e.textContent).toBe('');
+ });
+
+ itRenders('with context passed through to a grandchild', async render => {
+ class Grandchild extends React.Component {
+ static contextType = Context;
+ render() {
+ return {this.context}
;
+ }
+ }
+
+ const Child = props => ;
+
+ const e = await render(
+
+
+ ,
+ );
+ expect(e.textContent).toBe('purple');
+ });
+
+ itRenders('a child context overriding a parent context', async render => {
+ class Grandchild extends React.Component {
+ static contextType = Context;
+ render() {
+ return {this.context}
;
+ }
+ }
+
+ const e = await render(
+
+
+
+
+ ,
+ );
+ expect(e.textContent).toBe('red');
+ });
+
+ itRenders('multiple contexts', async render => {
+ const Theme = React.createContext('dark');
+ const Language = React.createContext('french');
+ class Parent extends React.Component {
+ render() {
+ return (
+
+
+
+ );
+ }
+ }
+
+ function Child() {
+ return (
+
+
+
+ );
+ }
+
+ class ThemeComponent extends React.Component {
+ static contextType = Theme;
+ render() {
+ return {this.context}
;
+ }
+ }
+
+ class LanguageComponent extends React.Component {
+ static contextType = Language;
+ render() {
+ return {this.context}
;
+ }
+ }
+
+ const Grandchild = props => {
+ return (
+
+
+
+
+ );
+ };
+
+ const e = await render();
+ expect(e.querySelector('#theme').textContent).toBe('light');
+ expect(e.querySelector('#language').textContent).toBe('english');
+ });
+
+ itRenders('nested context unwinding', async render => {
+ const Theme = React.createContext('dark');
+ const Language = React.createContext('french');
+
+ class ThemeConsumer extends React.Component {
+ static contextType = Theme;
+ render() {
+ return this.props.children(this.context);
+ }
+ }
+
+ class LanguageConsumer extends React.Component {
+ static contextType = Language;
+ render() {
+ return this.props.children(this.context);
+ }
+ }
+
+ const App = () => (
+
+
+
+
+
+ {theme => {theme}
}
+
+
+
+ {theme => {theme}
}
+
+
+
+
+
+ {() => (
+
+
+
+ {language => {language}
}
+
+
+ )}
+
+
+
+ {language => (
+
+
+ {theme => {theme}
}
+
+ {language}
+
+ )}
+
+
+
+
+
+
+ {language => {language}
}
+
+
+ );
+ let e = await render();
+ expect(e.querySelector('#theme1').textContent).toBe('dark');
+ expect(e.querySelector('#theme2').textContent).toBe('light');
+ expect(e.querySelector('#theme3').textContent).toBe('blue');
+ expect(e.querySelector('#language1').textContent).toBe('chinese');
+ expect(e.querySelector('#language2').textContent).toBe('sanskrit');
+ expect(e.querySelector('#language3').textContent).toBe('french');
+ });
+ });
+});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index faa24884d169d..59d855bbaeeb9 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -180,6 +180,7 @@ const didWarnAboutBadClass = {};
const didWarnAboutDeprecatedWillMount = {};
const didWarnAboutUndefinedDerivedState = {};
const didWarnAboutUninitializedState = {};
+const didWarnAboutInvalidateContextType = {};
const valuePropNames = ['value', 'defaultValue'];
const newlineEatingTags = {
listing: true,
@@ -357,13 +358,33 @@ function checkContextTypes(typeSpecs, values, location: string) {
}
function processContext(type, context) {
- const maskedContext = maskContext(type, context);
- if (__DEV__) {
- if (type.contextTypes) {
- checkContextTypes(type.contextTypes, maskedContext, 'context');
+ const contextType = type.contextType;
+ if (typeof contextType === 'object' && contextType !== null) {
+ if (__DEV__) {
+ if (contextType.$$typeof !== REACT_CONTEXT_TYPE) {
+ let name = getComponentName(type);
+ if (!didWarnAboutInvalidateContextType[name]) {
+ didWarnAboutInvalidateContextType[type] = true;
+ warningWithoutStack(
+ false,
+ '%s defines an invalid contextType. ' +
+ 'contextType should point to the Context object returned by React.createContext(). ' +
+ 'Did you accidentally pass the Context.Provider instead?',
+ name || 'Component',
+ );
+ }
+ }
}
+ return contextType._currentValue;
+ } else {
+ const maskedContext = maskContext(type, context);
+ if (__DEV__) {
+ if (type.contextTypes) {
+ checkContextTypes(type.contextTypes, maskedContext, 'context');
+ }
+ }
+ return maskedContext;
}
- return maskedContext;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index c2a87d8e9947c..57114af3e9f58 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -25,9 +25,11 @@ import shallowEqual from 'shared/shallowEqual';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
+import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {StrictMode} from './ReactTypeOfMode';
+
import {
enqueueUpdate,
processUpdateQueue,
@@ -516,7 +518,7 @@ function constructClassInstance(
if (typeof contextType === 'object' && contextType !== null) {
if (__DEV__) {
if (
- contextType.Consumer === undefined &&
+ contextType.$$typeof !== REACT_CONTEXT_TYPE &&
!didWarnAboutInvalidateContextType.has(ctor)
) {
didWarnAboutInvalidateContextType.add(ctor);