From 5696782b428a5ace96e66c1857e13249b6c07958 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 3 Feb 2024 19:02:13 +0000 Subject: [PATCH] Switch to mean --- .../src/ReactFlightReplyClient.js | 4 +- .../react-debug-tools/src/ReactDebugHooks.js | 4 +- ...eactDOMServerIntegrationNewContext-test.js | 129 +++--------------- .../__tests__/ReactServerRendering-test.js | 23 ++-- packages/react-is/src/ReactIs.js | 12 +- packages/react-reconciler/src/ReactFiber.js | 6 +- .../src/ReactFiberBeginWork.js | 39 +----- .../src/ReactFiberClassComponent.js | 10 +- .../src/ReactFiberCompleteWork.js | 2 +- .../src/ReactFiberNewContext.js | 5 +- .../react-reconciler/src/ReactFiberScope.js | 2 +- .../src/ReactFiberUnwindWork.js | 4 +- .../src/__tests__/ReactNewContext-test.js | 109 ++------------- .../src/getComponentNameFromFiber.js | 10 +- .../src/ReactFizzClassComponent.js | 10 +- packages/react-server/src/ReactFizzServer.js | 40 +----- packages/react/src/ReactContext.js | 95 +------------ packages/react/src/ReactHooks.js | 22 +-- .../__tests__/ReactContextValidator-test.js | 38 ++---- packages/shared/ReactSymbols.js | 2 +- packages/shared/ReactTypes.js | 10 +- packages/shared/getComponentNameFromType.js | 12 +- packages/shared/isValidElementType.js | 4 +- 23 files changed, 112 insertions(+), 480 deletions(-) diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index a83335e63e5b9..6bf4e11a43d53 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -18,7 +18,7 @@ import type { import { REACT_ELEMENT_TYPE, REACT_LAZY_TYPE, - REACT_PROVIDER_TYPE, + REACT_CONTEXT_TYPE, getIteratorFn, } from 'shared/ReactSymbols'; @@ -297,7 +297,7 @@ export function processReply( 'React Lazy cannot be passed to Server Functions from the Client.%s', describeObjectForErrorMessage(parent, key), ); - } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) { + } else if ((value: any).$$typeof === REACT_CONTEXT_TYPE) { console.error( 'React Context Providers cannot be passed to Server Functions from the Client.%s', describeObjectForErrorMessage(parent, key), diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index b4b0f97e2edaa..999cae9a59601 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -10,7 +10,6 @@ import type { Awaited, ReactContext, - ReactProviderType, StartTransitionOptions, } from 'shared/ReactTypes'; import type { @@ -797,8 +796,7 @@ function setupContexts(contextMap: Map, any>, fiber: Fiber) { let current: null | Fiber = fiber; while (current) { if (current.tag === ContextProvider) { - const providerType: ReactProviderType = current.type; - const context: ReactContext = providerType._context; + const context: ReactContext = current.type; if (!contextMap.has(context)) { // Store the current value that we're going to restore later. contextMap.set(context, context._currentValue); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index cf0167eef1fd2..5fbe35f5e7a7b 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js @@ -33,8 +33,7 @@ function initModules() { }; } -const {resetModules, itRenders, clientRenderOnBadMarkup} = - ReactDOMServerIntegrationUtils(initModules); +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); describe('ReactDOMServerIntegration', () => { beforeEach(() => { @@ -298,115 +297,29 @@ describe('ReactDOMServerIntegration', () => { 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'); + itRenders('should treat Context as Context.Provider', async render => { + const Theme = React.createContext('dark'); + const Language = React.createContext('french'); - return ( -
- - - - - {theme =>
{theme}
} -
-
-
-
-
- ); - }; - await render( - , - render === clientRenderOnBadMarkup - ? // On hydration mismatch we retry and therefore log the warning again. - 2 - : 1, - ); - }, - ); + expect(Theme.Provider).toBe(Theme); - 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'); + const App = () => ( +
+ + + + + {theme =>
{theme}
} +
+
+
+
+
+ ); - return ( -
- - - - - {theme =>
{theme}
} -
-
-
-
-
- ); - }; - - await render( - , - render === clientRenderOnBadMarkup - ? // On hydration mismatch we retry and therefore log the warning again. - 2 - : 1, - ); - }, - ); + const e = await render(, 0); + expect(e.textContent).toBe('dark'); + }); it('does not pollute parallel node streams', () => { const LoggedInUser = React.createContext(); diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 02f02187cb351..c3ef5043395cb 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -1003,20 +1003,12 @@ describe('ReactDOMServer', () => { it('should warn if an invalid contextType is defined', () => { const Context = React.createContext(); - class ComponentA extends React.Component { - // It should warn for both Context.Consumer and Context.Provider static contextType = Context.Consumer; render() { return
; } } - class ComponentB extends React.Component { - static contextType = Context.Provider; - render() { - return
; - } - } expect(() => { ReactDOMServer.renderToString(); @@ -1029,13 +1021,14 @@ describe('ReactDOMServer', () => { // Warnings should be deduped by component type ReactDOMServer.renderToString(); - expect(() => { - ReactDOMServer.renderToString(); - }).toErrorDev( - 'Warning: ComponentB defines an invalid contextType. ' + - 'contextType should point to the Context object returned by React.createContext(). ' + - 'Did you accidentally pass the Context.Provider instead?', - ); + class ComponentB extends React.Component { + static contextType = Context.Provider; + render() { + return
; + } + } + // Does not warn because Context === Context.Provider. + ReactDOMServer.renderToString(); }); it('should not warn when class contextType is null', () => { diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index eb68e2cbaceed..0ae91678083a5 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -18,7 +18,7 @@ import { REACT_MEMO_TYPE, REACT_PORTAL_TYPE, REACT_PROFILER_TYPE, - REACT_PROVIDER_TYPE, + REACT_CONSUMER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, @@ -47,7 +47,7 @@ export function typeOf(object: any): mixed { case REACT_FORWARD_REF_TYPE: case REACT_LAZY_TYPE: case REACT_MEMO_TYPE: - case REACT_PROVIDER_TYPE: + case REACT_CONSUMER_TYPE: return $$typeofType; default: return $$typeof; @@ -61,8 +61,8 @@ export function typeOf(object: any): mixed { return undefined; } -export const ContextConsumer = REACT_CONTEXT_TYPE; -export const ContextProvider = REACT_PROVIDER_TYPE; +export const ContextConsumer = REACT_CONSUMER_TYPE; +export const ContextProvider = REACT_CONTEXT_TYPE; export const Element = REACT_ELEMENT_TYPE; export const ForwardRef = REACT_FORWARD_REF_TYPE; export const Fragment = REACT_FRAGMENT_TYPE; @@ -107,10 +107,10 @@ export function isConcurrentMode(object: any): boolean { return false; } export function isContextConsumer(object: any): boolean { - return typeOf(object) === REACT_CONTEXT_TYPE; + return typeOf(object) === REACT_CONSUMER_TYPE; } export function isContextProvider(object: any): boolean { - return typeOf(object) === REACT_PROVIDER_TYPE; + return typeOf(object) === REACT_CONTEXT_TYPE; } export function isElement(object: any): boolean { return ( diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 573e497454028..3f147ff54dbc3 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -94,8 +94,8 @@ import { REACT_DEBUG_TRACING_MODE_TYPE, REACT_STRICT_MODE_TYPE, REACT_PROFILER_TYPE, - REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, REACT_MEMO_TYPE, @@ -581,10 +581,10 @@ export function createFiberFromTypeAndProps( default: { if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { - case REACT_PROVIDER_TYPE: + case REACT_CONTEXT_TYPE: fiberTag = ContextProvider; break getTag; - case REACT_CONTEXT_TYPE: + case REACT_CONSUMER_TYPE: // This is a consumer fiberTag = ContextConsumer; break getTag; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 5c555b1812d43..12d200fe5fd77 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -8,7 +8,7 @@ */ import type { - ReactProviderType, + ReactConsumerType, ReactContext, ReactNodeList, } from 'shared/ReactTypes'; @@ -3532,9 +3532,7 @@ function updateContextProvider( workInProgress: Fiber, renderLanes: Lanes, ) { - const providerType: ReactProviderType = workInProgress.type; - const context: ReactContext = providerType._context; - + const context: ReactContext = workInProgress.type; const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps; @@ -3591,39 +3589,14 @@ function updateContextProvider( return workInProgress.child; } -let hasWarnedAboutUsingContextAsConsumer = false; - function updateContextConsumer( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ) { - let context: ReactContext = workInProgress.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 ((context: 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 (context !== context.Consumer) { - if (!hasWarnedAboutUsingContextAsConsumer) { - hasWarnedAboutUsingContextAsConsumer = true; - console.error( - 'Rendering directly is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - } - } - } else { - context = (context: any)._context; - } - } + const consumerType: ReactConsumerType = workInProgress.type; + const context = consumerType._context; + const newProps = workInProgress.pendingProps; const render = newProps.children; @@ -3869,7 +3842,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate( break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; - const context: ReactContext = workInProgress.type._context; + const context: ReactContext = workInProgress.type; pushProvider(workInProgress, context, newValue); break; } diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index cceb78c8ed878..a38c859d804ab 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -32,7 +32,7 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom import getComponentNameFromType from 'shared/getComponentNameFromType'; import assign from 'shared/assign'; import isArray from 'shared/isArray'; -import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; +import {REACT_CONTEXT_TYPE, REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import {resolveDefaultProps} from './ReactFiberLazyComponent'; import { @@ -596,8 +596,7 @@ function constructClassInstance( // Allow null for conditional declaration contextType === null || (contextType !== undefined && - contextType.$$typeof === REACT_CONTEXT_TYPE && - contextType._context === undefined); // Not a + contextType.$$typeof === REACT_CONTEXT_TYPE); if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) { didWarnAboutInvalidateContextType.add(ctor); @@ -611,10 +610,7 @@ function constructClassInstance( '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) { - // + } else if (contextType.$$typeof === REACT_CONSUMER_TYPE) { addendum = ' Did you accidentally pass the Context.Consumer instead?'; } else { addendum = diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 78dd30cd5f325..9a4269af8f38d 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -1505,7 +1505,7 @@ function completeWork( return null; case ContextProvider: // Pop provider fiber - const context: ReactContext = workInProgress.type._context; + const context: ReactContext = workInProgress.type; popProvider(context, workInProgress); bubbleProperties(workInProgress); return null; diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 0aec54348c2cd..3c1ff79e25149 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -7,7 +7,7 @@ * @flow */ -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes'; +import type {ReactContext} from 'shared/ReactTypes'; import type { Fiber, ContextDependency, @@ -561,8 +561,7 @@ function propagateParentContextChanges( const oldProps = currentParent.memoizedProps; if (oldProps !== null) { - const providerType: ReactProviderType = parent.type; - const context: ReactContext = providerType._context; + const context: ReactContext = parent.type; const newProps = parent.pendingProps; const newValue = newProps.value; diff --git a/packages/react-reconciler/src/ReactFiberScope.js b/packages/react-reconciler/src/ReactFiberScope.js index a95518aacb31c..8f9f1cdea577c 100644 --- a/packages/react-reconciler/src/ReactFiberScope.js +++ b/packages/react-reconciler/src/ReactFiberScope.js @@ -113,7 +113,7 @@ function collectNearestContextValues( context: ReactContext, childContextValues: Array, ): void { - if (node.tag === ContextProvider && node.type._context === context) { + if (node.tag === ContextProvider && node.type === context) { const contextValue = node.memoizedProps.value; childContextValues.push(contextValue); } else { diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index fdbd3357ed3c3..d9a9eb5c77860 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -160,7 +160,7 @@ function unwindWork( popHostContainer(workInProgress); return null; case ContextProvider: - const context: ReactContext = workInProgress.type._context; + const context: ReactContext = workInProgress.type; popProvider(context, workInProgress); return null; case OffscreenComponent: @@ -250,7 +250,7 @@ function unwindInterruptedWork( popSuspenseListContext(interruptedWork); break; case ContextProvider: - const context: ReactContext = interruptedWork.type._context; + const context: ReactContext = interruptedWork.type; popProvider(context, interruptedWork); break; case OffscreenComponent: diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js index 61a5a273b9dfc..8c6476345beeb 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -1348,21 +1348,7 @@ describe('ReactNewContext', () => { } ReactNoop.render(); await expect(async () => await waitForAll([])).toErrorDev( - 'Calling useContext(Context.Consumer) is not supported, may cause bugs, ' + - 'and will be removed in a future major release. ' + - 'Did you mean to call useContext(Context) instead?', - ); - }); - - it('warns when passed a provider', async () => { - const Context = React.createContext(0); - function Foo() { - useContext(Context.Provider); - return null; - } - ReactNoop.render(); - await expect(async () => await waitForAll([])).toErrorDev( - 'Calling useContext(Context.Provider) is not supported. ' + + 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' + 'Did you mean to call useContext(Context) instead?', ); }); @@ -1651,99 +1637,22 @@ Context fuzz tester error! Copy and paste the following line into the test suite }); }); - it('should warn with an error message when using context as a consumer in DEV', async () => { - const BarContext = React.createContext({value: 'bar-initial'}); - const BarConsumer = BarContext; - - function Component() { - return ( - <> - - - {({value}) =>
} - - - - ); - } - - await expect(async () => { - ReactNoop.render(); - await waitForAll([]); - }).toErrorDev( - 'Rendering directly is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - }); - - // False positive regression test. - it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => { + it('should treat Context as Context.Provider', async () => { const BarContext = React.createContext({value: 'bar-initial'}); - // React 16.5 and earlier didn't have a separate object. - BarContext.Consumer = BarContext; + expect(BarContext.Provider).toBe(BarContext); function Component() { return ( - <> - - - {({value}) =>
} - - - + + + {({value}) => } + + ); } ReactNoop.render(); await waitForAll([]); - }); - - it('should warn with an error message when using nested context consumers in DEV', async () => { - const BarContext = React.createContext({value: 'bar-initial'}); - const BarConsumer = BarContext; - - function Component() { - return ( - <> - - - {({value}) =>
} - - - - ); - } - - await expect(async () => { - ReactNoop.render(); - await waitForAll([]); - }).toErrorDev( - 'Rendering is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - }); - - it('should warn with an error message when using Context.Consumer.Provider DEV', async () => { - const BarContext = React.createContext({value: 'bar-initial'}); - - function Component() { - return ( - <> - - - {({value}) =>
} - - - - ); - } - - await expect(async () => { - ReactNoop.render(); - await waitForAll([]); - }).toErrorDev( - 'Rendering is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); + expect(ReactNoop).toMatchRenderedOutput(); }); }); diff --git a/packages/react-reconciler/src/getComponentNameFromFiber.js b/packages/react-reconciler/src/getComponentNameFromFiber.js index f8b2388da9073..61b100a856429 100644 --- a/packages/react-reconciler/src/getComponentNameFromFiber.js +++ b/packages/react-reconciler/src/getComponentNameFromFiber.js @@ -7,7 +7,7 @@ * @flow */ -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes'; +import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes'; import type {Fiber} from './ReactInternalTypes'; import {enableLegacyHidden} from 'shared/ReactFeatureFlags'; @@ -68,11 +68,11 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null { case CacheComponent: return 'Cache'; case ContextConsumer: - const context: ReactContext = (type: any); - return getContextName(context) + '.Consumer'; + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; case ContextProvider: - const provider: ReactProviderType = (type: any); - return getContextName(provider._context) + '.Provider'; + const context: ReactContext = (type: any); + return getContextName(context) + '.Provider'; case DehydratedFragment: return 'DehydratedFragment'; case ForwardRef: diff --git a/packages/react-server/src/ReactFizzClassComponent.js b/packages/react-server/src/ReactFizzClassComponent.js index e624dfa941293..7d528348d6c88 100644 --- a/packages/react-server/src/ReactFizzClassComponent.js +++ b/packages/react-server/src/ReactFizzClassComponent.js @@ -13,7 +13,7 @@ import {readContext} from './ReactFizzNewContext'; import {disableLegacyContext} from 'shared/ReactFeatureFlags'; import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap'; import getComponentNameFromType from 'shared/getComponentNameFromType'; -import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; +import {REACT_CONTEXT_TYPE, REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import assign from 'shared/assign'; import isArray from 'shared/isArray'; @@ -181,8 +181,7 @@ export function constructClassInstance( // Allow null for conditional declaration contextType === null || (contextType !== undefined && - contextType.$$typeof === REACT_CONTEXT_TYPE && - contextType._context === undefined); // Not a + contextType.$$typeof === REACT_CONTEXT_TYPE); if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) { didWarnAboutInvalidateContextType.add(ctor); @@ -196,10 +195,7 @@ export function constructClassInstance( '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) { - // + } else if (contextType.$$typeof === REACT_CONSUMER_TYPE) { addendum = ' Did you accidentally pass the Context.Consumer instead?'; } else { addendum = diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index dee3fe5bfcfff..d76b906ff7494 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -15,7 +15,7 @@ import type { import type { ReactNodeList, ReactContext, - ReactProviderType, + ReactConsumerType, OffscreenMode, Wakeable, Thenable, @@ -127,8 +127,8 @@ import { REACT_FRAGMENT_TYPE, REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE, - REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_SCOPE_TYPE, REACT_OFFSCREEN_TYPE, REACT_POSTPONE_TYPE, @@ -1393,7 +1393,6 @@ let didWarnAboutReassigningProps = false; const didWarnAboutDefaultPropsOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutGenerators = false; let didWarnAboutMaps = false; -let hasWarnedAboutUsingContextAsConsumer = false; // This would typically be a function component but we still support module pattern // components for some reason. @@ -1700,34 +1699,10 @@ function renderContextConsumer( request: Request, task: Task, keyPath: KeyNode, - context: ReactContext, + type: ReactConsumerType, props: Object, ): void { - // 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 ((context: 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 (context !== context.Consumer) { - if (!hasWarnedAboutUsingContextAsConsumer) { - hasWarnedAboutUsingContextAsConsumer = true; - console.error( - 'Rendering directly is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - } - } - } else { - context = (context: any)._context; - } - } + const context = type._context; const render = props.children; if (__DEV__) { @@ -1754,10 +1729,9 @@ function renderContextProvider( request: Request, task: Task, keyPath: KeyNode, - type: ReactProviderType, + context: ReactContext, props: Object, ): void { - const context = type._context; const value = props.value; const children = props.children; let prevSnapshot; @@ -1908,11 +1882,11 @@ function renderElement( renderMemo(request, task, keyPath, type, props, ref); return; } - case REACT_PROVIDER_TYPE: { + case REACT_CONTEXT_TYPE: { renderContextProvider(request, task, keyPath, type, props); return; } - case REACT_CONTEXT_TYPE: { + case REACT_CONSUMER_TYPE: { renderContextConsumer(request, task, keyPath, type, props); return; } diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index 90e73ec46d065..5f4fc2585c2b7 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -7,9 +7,8 @@ * @flow */ -import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import {REACT_CONSUMER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; -import type {ReactProviderType} from 'shared/ReactTypes'; import type {ReactContext} from 'shared/ReactTypes'; export function createContext(defaultValue: T): ReactContext { @@ -33,98 +32,12 @@ export function createContext(defaultValue: T): ReactContext { Consumer: (null: any), }; - context.Provider = { - $$typeof: REACT_PROVIDER_TYPE, + context.Provider = context; + context.Consumer = { + $$typeof: REACT_CONSUMER_TYPE, _context: context, }; - let hasWarnedAboutUsingNestedContextConsumers = false; - let hasWarnedAboutUsingConsumerProvider = false; - let hasWarnedAboutDisplayNameOnConsumer = false; - - if (__DEV__) { - // A separate object, but proxies back to the original context object for - // backwards compatibility. It has a different $$typeof, so we can properly - // warn for the incorrect usage of Context as a Consumer. - const Consumer = { - $$typeof: REACT_CONTEXT_TYPE, - _context: context, - }; - // $FlowFixMe[prop-missing]: Flow complains about not setting a value, which is intentional here - Object.defineProperties(Consumer, { - Provider: { - get() { - if (!hasWarnedAboutUsingConsumerProvider) { - hasWarnedAboutUsingConsumerProvider = true; - console.error( - 'Rendering is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - } - return context.Provider; - }, - set(_Provider: ReactProviderType) { - context.Provider = _Provider; - }, - }, - _currentValue: { - get() { - return context._currentValue; - }, - set(_currentValue: T) { - context._currentValue = _currentValue; - }, - }, - _currentValue2: { - get() { - return context._currentValue2; - }, - set(_currentValue2: T) { - context._currentValue2 = _currentValue2; - }, - }, - _threadCount: { - get() { - return context._threadCount; - }, - set(_threadCount: number) { - context._threadCount = _threadCount; - }, - }, - Consumer: { - get() { - if (!hasWarnedAboutUsingNestedContextConsumers) { - hasWarnedAboutUsingNestedContextConsumers = true; - console.error( - 'Rendering is not supported and will be removed in ' + - 'a future major release. Did you mean to render instead?', - ); - } - return context.Consumer; - }, - }, - displayName: { - get() { - return context.displayName; - }, - set(displayName: void | string) { - if (!hasWarnedAboutDisplayNameOnConsumer) { - console.warn( - 'Setting `displayName` on Context.Consumer has no effect. ' + - "You should set it directly on the context with Context.displayName = '%s'.", - displayName, - ); - hasWarnedAboutDisplayNameOnConsumer = true; - } - }, - }, - }); - // $FlowFixMe[prop-missing]: Flow complains about missing properties because it doesn't understand defineProperty - context.Consumer = Consumer; - } else { - context.Consumer = context; - } - if (__DEV__) { context._currentRenderer = null; context._currentRenderer2 = null; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index a2141cbc5dfbe..3d67608c87d75 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -13,6 +13,7 @@ import type { StartTransitionOptions, Usable, } from 'shared/ReactTypes'; +import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import ReactCurrentDispatcher from './ReactCurrentDispatcher'; import ReactCurrentCache from './ReactCurrentCache'; @@ -72,22 +73,11 @@ export function getCacheForType(resourceType: () => T): T { export function useContext(Context: ReactContext): T { const dispatcher = resolveDispatcher(); if (__DEV__) { - // TODO: add a more generic warning for invalid values. - if ((Context: any)._context !== undefined) { - const realContext = (Context: any)._context; - // Don't deduplicate because this legitimately causes bugs - // and nobody should be using this in existing code. - if (realContext.Consumer === Context) { - console.error( - 'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' + - 'removed in a future major release. Did you mean to call useContext(Context) instead?', - ); - } else if (realContext.Provider === Context) { - console.error( - 'Calling useContext(Context.Provider) is not supported. ' + - 'Did you mean to call useContext(Context) instead?', - ); - } + if (Context.$$typeof === REACT_CONSUMER_TYPE) { + console.error( + 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' + + 'Did you mean to call useContext(Context) instead?', + ); } } return dispatcher.useContext(Context); diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js index ac6fc10343d04..d20d8e4042779 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -568,20 +568,12 @@ describe('ReactContextValidator', () => { it('should warn if an invalid contextType is defined', () => { const Context = React.createContext(); - // This tests that both Context.Consumer and Context.Provider - // warn about invalid contextType. class ComponentA extends React.Component { static contextType = Context.Consumer; render() { return
; } } - class ComponentB extends React.Component { - static contextType = Context.Provider; - render() { - return
; - } - } expect(() => { ReactTestUtils.renderIntoDocument(); @@ -594,13 +586,14 @@ describe('ReactContextValidator', () => { // Warnings should be deduped by component type ReactTestUtils.renderIntoDocument(); - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).toErrorDev( - 'Warning: ComponentB defines an invalid contextType. ' + - 'contextType should point to the Context object returned by React.createContext(). ' + - 'Did you accidentally pass the Context.Provider instead?', - ); + class ComponentB extends React.Component { + static contextType = Context.Provider; + render() { + return
; + } + } + // This doesn't warn since Context.Provider === Context now. + ReactTestUtils.renderIntoDocument(); }); it('should not warn when class contextType is null', () => { @@ -725,19 +718,4 @@ describe('ReactContextValidator', () => { ' in Validator (at **)', ); }); - - it('warns if displayName is set on the consumer type', () => { - const Context = React.createContext(null); - - expect(() => { - Context.Consumer.displayName = 'IgnoredName'; - }).toWarnDev( - 'Warning: Setting `displayName` on Context.Consumer has no effect. ' + - "You should set it directly on the context with Context.displayName = 'IgnoredName'.", - {withoutStack: true}, - ); - - // warning is deduped by Context so subsequent setting is fine - Context.Consumer.displayName = 'ADifferentName'; - }); }); diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 133a84953ea63..8f7de70ea2ad7 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -17,7 +17,7 @@ export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal'); export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment'); export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode'); export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler'); -export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider'); +export const REACT_CONSUMER_TYPE: symbol = Symbol.for('react.consumer'); export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context'); export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref'); export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense'); diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 1fe711b779bf3..252c8245bcf5b 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -25,7 +25,7 @@ export type ReactText = string | number; export type ReactProvider = { $$typeof: symbol | number, - type: ReactProviderType, + type: ReactContext, key: null | string, ref: null, props: { @@ -34,14 +34,14 @@ export type ReactProvider = { }, }; -export type ReactProviderType = { +export type ReactConsumerType = { $$typeof: symbol | number, _context: ReactContext, }; export type ReactConsumer = { $$typeof: symbol | number, - type: ReactContext, + type: ReactConsumerType, key: null | string, ref: null, props: { @@ -51,8 +51,8 @@ export type ReactConsumer = { export type ReactContext = { $$typeof: symbol | number, - Consumer: ReactContext, - Provider: ReactProviderType, + Consumer: ReactConsumerType, + Provider: ReactContext, _currentValue: T, _currentValue2: T, _threadCount: number, diff --git a/packages/shared/getComponentNameFromType.js b/packages/shared/getComponentNameFromType.js index 310d4adc595dc..390c46ff0a14e 100644 --- a/packages/shared/getComponentNameFromType.js +++ b/packages/shared/getComponentNameFromType.js @@ -8,16 +8,16 @@ */ import type {LazyComponent} from 'react/src/ReactLazy'; -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes'; +import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes'; import { REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_PORTAL_TYPE, REACT_MEMO_TYPE, REACT_PROFILER_TYPE, - REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, @@ -100,10 +100,10 @@ export default function getComponentNameFromType(type: mixed): string | null { switch (type.$$typeof) { case REACT_CONTEXT_TYPE: const context: ReactContext = (type: any); - return getContextName(context) + '.Consumer'; - case REACT_PROVIDER_TYPE: - const provider: ReactProviderType = (type: any); - return getContextName(provider._context) + '.Provider'; + return getContextName(context) + '.Provider'; + case REACT_CONSUMER_TYPE: + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); case REACT_MEMO_TYPE: diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js index 5b8511e084dba..4529e71f3f393 100644 --- a/packages/shared/isValidElementType.js +++ b/packages/shared/isValidElementType.js @@ -9,10 +9,10 @@ import { REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, - REACT_PROVIDER_TYPE, REACT_DEBUG_TRACING_MODE_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, @@ -61,8 +61,8 @@ export default function isValidElementType(type: mixed): boolean { if ( type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || - type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || + type.$$typeof === REACT_CONSUMER_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object // types supported by any Flight configuration anywhere since