diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index af9a6095e0a46..ee2afb4fae371 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js @@ -249,5 +249,104 @@ describe('ReactDOMServerIntegration', () => { expect(e.querySelector('#language2').textContent).toBe('sanskrit'); expect(e.querySelector('#language3').textContent).toBe('french'); }); + + itRenders( + 'should warn with an error message when using Context as consumer in DEV', + async render => { + const Theme = React.createContext('dark'); + const Language = React.createContext('french'); + + const App = () => ( +
+ + + + {theme =>
{theme}
}
+
+
+
+
+ ); + // We expect 1 error. + await render(, 1); + }, + ); + + // False positive regression test. + itRenders( + 'should not warn when using Consumer from React < 16.6 with newer renderer', + async render => { + const Theme = React.createContext('dark'); + const Language = React.createContext('french'); + // React 16.5 and earlier didn't have a separate object. + Theme.Consumer = Theme; + + const App = () => ( +
+ + + + {theme =>
{theme}
}
+
+
+
+
+ ); + // We expect 0 errors. + await render(, 0); + }, + ); + + itRenders( + 'should warn with an error message when using nested context consumers in DEV', + async render => { + const App = () => { + const Theme = React.createContext('dark'); + const Language = React.createContext('french'); + + return ( +
+ + + + + {theme =>
{theme}
} +
+
+
+
+
+ ); + }; + // We expect 1 error. + await render(, 1); + }, + ); + + itRenders( + 'should warn with an error message when using Context.Consumer.Provider DEV', + async render => { + const App = () => { + const Theme = React.createContext('dark'); + const Language = React.createContext('french'); + + return ( +
+ + + + + {theme =>
{theme}
} +
+
+
+
+
+ ); + }; + // We expect 1 error. + await render(, 1); + }, + ); }); }); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 1ee885c68abc2..a905f972ebe3f 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -8,11 +8,7 @@ */ import type {ReactElement} from 'shared/ReactElementType'; -import type { - ReactProvider, - ReactConsumer, - ReactContext, -} from 'shared/ReactTypes'; +import type {ReactProvider, ReactContext} from 'shared/ReactTypes'; import React from 'react'; import invariant from 'shared/invariant'; @@ -91,6 +87,7 @@ let validatePropertiesInDevelopment = (type, props) => {}; let pushCurrentDebugStack = (stack: Array) => {}; let pushElementToDebugStack = (element: ReactElement) => {}; let popCurrentDebugStack = () => {}; +let hasWarnedAboutUsingContextAsConsumer = false; if (__DEV__) { ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; @@ -1054,9 +1051,35 @@ class ReactDOMServerRenderer { return ''; } case REACT_CONTEXT_TYPE: { - const consumer: ReactConsumer = (nextChild: any); - const nextProps: any = consumer.props; - const nextValue = consumer.type._currentValue; + let reactContext = (nextChild: any).type; + // The logic below for Context differs depending on PROD or DEV mode. In + // DEV mode, we create a separate object for Context.Consumer that acts + // like a proxy to Context. This proxy object adds unnecessary code in PROD + // so we use the old behaviour (Context.Consumer references Context) to + // reduce size and overhead. The separate object references context via + // a property called "_context", which also gives us the ability to check + // in DEV mode if this property exists or not and warn if it does not. + if (__DEV__) { + if ((reactContext: any)._context === undefined) { + // This may be because it's a Context (rather than a Consumer). + // Or it may be because it's older React where they're the same thing. + // We only want to warn if we're sure it's a new React. + if (reactContext !== reactContext.Consumer) { + if (!hasWarnedAboutUsingContextAsConsumer) { + hasWarnedAboutUsingContextAsConsumer = true; + warning( + false, + 'Rendering directly is not supported and will be removed in ' + + 'a future major release. Did you mean to render instead?', + ); + } + } + } else { + reactContext = (reactContext: any)._context; + } + } + const nextProps: any = (nextChild: any).props; + const nextValue = reactContext._currentValue; const nextChildren = toArray(nextProps.children(nextValue)); const frame: Frame = {