From 14fd9630ee04387f4361da289393234e2b7d93b6 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 13 Feb 2024 15:04:49 +0000 Subject: [PATCH] Switch to mean (#28226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, `` was equivalent to ``. However, since the introduction of Hooks, the `` API is rarely used. The goal here is to make the common case cleaner: ```js const ThemeContext = createContext('light') function App() { return ( ... ) } function Button() { const theme = use(ThemeContext) // ... } ``` This is technically a breaking change, but we've been warning about rendering `` directly for several years by now, so it's unlikely much code in the wild depends on the old behavior. [Proof that it warns today (check console).](https://codesandbox.io/p/sandbox/peaceful-nobel-pdxtfl) --- **The relevant commit is 5696782b428a5ace96e66c1857e13249b6c07958.** It switches `createContext` implementation so that `Context.Provider === Context`. The main assumption that changed is that a Provider's fiber type is now the context itself (rather than an intermediate object). Whereas a Consumer's fiber type is now always an intermediate object (rather than it being sometimes the context itself and sometimes an intermediate object). My methodology was to start with the relevant symbols, work tags, and types, and work my way backwards to all usages. This might break tooling that depends on inspecting React's internal fields. I've added DevTools support in the second commit. This didn't need explicit versioning—the structure tells us enough. --- .../src/ReactFlightReplyClient.js | 7 +- .../react-debug-tools/src/ReactDebugHooks.js | 8 +- .../src/backend/ReactSymbols.js | 2 + .../src/backend/renderer.js | 53 ++++++- ...eactDOMServerIntegrationNewContext-test.js | 133 ++++------------ .../__tests__/ReactServerRendering-test.js | 24 ++- packages/react-is/src/ReactIs.js | 33 +++- packages/react-reconciler/src/ReactFiber.js | 25 ++- .../src/ReactFiberBeginWork.js | 53 +++---- .../src/ReactFiberClassComponent.js | 10 +- .../src/ReactFiberCompleteWork.js | 8 +- .../src/ReactFiberNewContext.js | 11 +- .../react-reconciler/src/ReactFiberScope.js | 10 +- .../src/ReactFiberUnwindWork.js | 15 +- .../src/__tests__/ReactNewContext-test.js | 111 ++----------- .../src/getComponentNameFromFiber.js | 25 ++- .../src/ReactFizzClassComponent.js | 10 +- packages/react-server/src/ReactFizzServer.js | 66 ++++---- packages/react/src/ReactContext.js | 147 ++++++++---------- packages/react/src/ReactHooks.js | 22 +-- .../__tests__/ReactContextValidator-test.js | 39 ++--- packages/shared/ReactFeatureFlags.js | 2 + packages/shared/ReactSymbols.js | 3 +- packages/shared/ReactTypes.js | 10 +- .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + packages/shared/getComponentNameFromType.js | 32 +++- packages/shared/isValidElementType.js | 7 +- 33 files changed, 400 insertions(+), 473 deletions(-) diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 31e3a3f17dda8..650980cc2c1e3 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -14,10 +14,12 @@ import type { RejectedThenable, ReactCustomFormAction, } from 'shared/ReactTypes'; +import {enableRenderableContext} from 'shared/ReactFeatureFlags'; import { REACT_ELEMENT_TYPE, REACT_LAZY_TYPE, + REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE, getIteratorFn, } from 'shared/ReactSymbols'; @@ -302,7 +304,10 @@ 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 === + (enableRenderableContext ? REACT_CONTEXT_TYPE : REACT_PROVIDER_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 962d7d8bba2b5..103cfa08f0ab0 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, Usable, Thenable, @@ -931,8 +930,11 @@ 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; + let context: ReactContext = current.type; + if ((context: any)._context !== undefined) { + // Support inspection of pre-19+ providers. + context = (context: any)._context; + } 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-devtools-shared/src/backend/ReactSymbols.js b/packages/react-devtools-shared/src/backend/ReactSymbols.js index 64f84cc913705..2a79ce83ae0a4 100644 --- a/packages/react-devtools-shared/src/backend/ReactSymbols.js +++ b/packages/react-devtools-shared/src/backend/ReactSymbols.js @@ -51,6 +51,8 @@ export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)'; export const PROVIDER_NUMBER = 0xeacd; export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)'; +export const CONSUMER_SYMBOL_STRING = 'Symbol(react.consumer)'; + export const SCOPE_NUMBER = 0xead7; export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)'; diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 069cde215a1ca..59b2059dc8a61 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -79,6 +79,7 @@ import { PROVIDER_SYMBOL_STRING, CONTEXT_NUMBER, CONTEXT_SYMBOL_STRING, + CONSUMER_SYMBOL_STRING, STRICT_MODE_NUMBER, STRICT_MODE_SYMBOL_STRING, PROFILER_NUMBER, @@ -525,6 +526,15 @@ export function getInternalReactConstants(version: string): { case CONTEXT_NUMBER: case CONTEXT_SYMBOL_STRING: case SERVER_CONTEXT_SYMBOL_STRING: + if ( + fiber.type._context === undefined && + fiber.type.Provider === fiber.type + ) { + // In 19+, Context.Provider === Context, so this is a provider. + resolvedContext = fiber.type; + return `${resolvedContext.displayName || 'Context'}.Provider`; + } + // 16.3-16.5 read from "type" because the Consumer is the actual context object. // 16.6+ should read from "type._context" because Consumer can be different (in DEV). // NOTE Keep in sync with inspectElementRaw() @@ -533,6 +543,10 @@ export function getInternalReactConstants(version: string): { // NOTE: TraceUpdatesBackendManager depends on the name ending in '.Consumer' // If you change the name, figure out a more resilient way to detect it. return `${resolvedContext.displayName || 'Context'}.Consumer`; + case CONSUMER_SYMBOL_STRING: + // 19+ + resolvedContext = fiber.type._context; + return `${resolvedContext.displayName || 'Context'}.Consumer`; case STRICT_MODE_NUMBER: case STRICT_MODE_SYMBOL_STRING: return null; @@ -3178,8 +3192,14 @@ export function attach( } } } else if ( - typeSymbol === CONTEXT_NUMBER || - typeSymbol === CONTEXT_SYMBOL_STRING + // Detect pre-19 Context Consumers + (typeSymbol === CONTEXT_NUMBER || typeSymbol === CONTEXT_SYMBOL_STRING) && + !( + // In 19+, CONTEXT_SYMBOL_STRING means a Provider instead. + // It will be handled in a different branch below. + // Eventually, this entire branch can be removed. + (type._context === undefined && type.Provider === type) + ) ) { // 16.3-16.5 read from "type" because the Consumer is the actual context object. // 16.6+ should read from "type._context" because Consumer can be different (in DEV). @@ -3209,6 +3229,35 @@ export function attach( } } + current = current.return; + } + } else if ( + // Detect 19+ Context Consumers + typeSymbol === CONSUMER_SYMBOL_STRING + ) { + // This branch is 19+ only, where Context.Provider === Context. + // NOTE Keep in sync with getDisplayNameForFiber() + const consumerResolvedContext = type._context; + + // Global context value. + context = consumerResolvedContext._currentValue || null; + + // Look for overridden value. + let current = ((fiber: any): Fiber).return; + while (current !== null) { + const currentType = current.type; + const currentTypeSymbol = getTypeSymbol(currentType); + if ( + // In 19+, these are Context Providers + currentTypeSymbol === CONTEXT_SYMBOL_STRING + ) { + const providerResolvedContext = currentType; + if (providerResolvedContext === consumerResolvedContext) { + context = current.memoizedProps.value; + break; + } + } + current = current.return; } } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index 718dc983cb371..a460e2104ce88 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js @@ -31,8 +31,7 @@ function initModules() { }; } -const {resetModules, itRenders, clientRenderOnBadMarkup} = - ReactDOMServerIntegrationUtils(initModules); +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); describe('ReactDOMServerIntegration', () => { beforeEach(() => { @@ -296,115 +295,35 @@ 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'); + itRenders('should treat Context as Context.Provider', async render => { + // The `itRenders` helpers don't work with the gate pragma, so we have to do + // this instead. + if (gate(flags => !flags.enableRenderableContext)) { + return; + } - 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'); + 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 c2c89c8093499..b95c20f9e3182 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -1000,22 +1000,15 @@ describe('ReactDOMServer', () => { ]); }); + // @gate enableRenderableContext || !__DEV__ 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(); @@ -1028,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 41a63d6407bea..6e935bd8f7c64 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -19,11 +19,13 @@ import { REACT_PORTAL_TYPE, REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, + REACT_CONSUMER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, } from 'shared/ReactSymbols'; import isValidElementType from 'shared/isValidElementType'; +import {enableRenderableContext} from 'shared/ReactFeatureFlags'; export function typeOf(object: any): mixed { if (typeof object === 'object' && object !== null) { @@ -47,8 +49,17 @@ export function typeOf(object: any): mixed { case REACT_FORWARD_REF_TYPE: case REACT_LAZY_TYPE: case REACT_MEMO_TYPE: - case REACT_PROVIDER_TYPE: return $$typeofType; + case REACT_CONSUMER_TYPE: + if (enableRenderableContext) { + return $$typeofType; + } + // Fall through + case REACT_PROVIDER_TYPE: + if (!enableRenderableContext) { + return $$typeofType; + } + // Fall through default: return $$typeof; } @@ -61,8 +72,12 @@ export function typeOf(object: any): mixed { return undefined; } -export const ContextConsumer = REACT_CONTEXT_TYPE; -export const ContextProvider = REACT_PROVIDER_TYPE; +export const ContextConsumer: symbol = enableRenderableContext + ? REACT_CONSUMER_TYPE + : REACT_CONTEXT_TYPE; +export const ContextProvider: symbol = enableRenderableContext + ? REACT_CONTEXT_TYPE + : REACT_PROVIDER_TYPE; export const Element = REACT_ELEMENT_TYPE; export const ForwardRef = REACT_FORWARD_REF_TYPE; export const Fragment = REACT_FRAGMENT_TYPE; @@ -77,10 +92,18 @@ export const SuspenseList = REACT_SUSPENSE_LIST_TYPE; export {isValidElementType}; export function isContextConsumer(object: any): boolean { - return typeOf(object) === REACT_CONTEXT_TYPE; + if (enableRenderableContext) { + return typeOf(object) === REACT_CONSUMER_TYPE; + } else { + return typeOf(object) === REACT_CONTEXT_TYPE; + } } export function isContextProvider(object: any): boolean { - return typeOf(object) === REACT_PROVIDER_TYPE; + if (enableRenderableContext) { + return typeOf(object) === REACT_CONTEXT_TYPE; + } else { + return typeOf(object) === REACT_PROVIDER_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 82a07c2bbcf24..982e8b5905456 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -38,6 +38,7 @@ import { enableDebugTracing, enableFloat, enableDO_NOT_USE_disableStrictPassiveEffect, + enableRenderableContext, } from 'shared/ReactFeatureFlags'; import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot} from './ReactRootTags'; @@ -96,6 +97,7 @@ import { REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, REACT_MEMO_TYPE, @@ -580,12 +582,25 @@ export function createFiberFromTypeAndProps( if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { case REACT_PROVIDER_TYPE: - fiberTag = ContextProvider; - break getTag; + if (!enableRenderableContext) { + fiberTag = ContextProvider; + break getTag; + } + // Fall through case REACT_CONTEXT_TYPE: - // This is a consumer - fiberTag = ContextConsumer; - break getTag; + if (enableRenderableContext) { + fiberTag = ContextProvider; + break getTag; + } else { + fiberTag = ContextConsumer; + break getTag; + } + case REACT_CONSUMER_TYPE: + if (enableRenderableContext) { + fiberTag = ContextConsumer; + break getTag; + } + // Fall through case REACT_FORWARD_REF_TYPE: fiberTag = ForwardRef; if (__DEV__) { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 3bd5253e71d02..ab05fc4eb7f05 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'; @@ -110,6 +110,7 @@ import { enableFormActions, enableAsyncActions, enablePostpone, + enableRenderableContext, } from 'shared/ReactFeatureFlags'; import isArray from 'shared/isArray'; import shallowEqual from 'shared/shallowEqual'; @@ -3528,9 +3529,12 @@ function updateContextProvider( workInProgress: Fiber, renderLanes: Lanes, ) { - const providerType: ReactProviderType = workInProgress.type; - const context: ReactContext = providerType._context; - + let context: ReactContext; + if (enableRenderableContext) { + context = workInProgress.type; + } else { + context = workInProgress.type._context; + } const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps; @@ -3587,37 +3591,21 @@ 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?', - ); - } + let context: ReactContext; + if (enableRenderableContext) { + const consumerType: ReactConsumerType = workInProgress.type; + context = consumerType._context; + } else { + context = workInProgress.type; + if (__DEV__) { + if ((context: any)._context !== undefined) { + context = (context: any)._context; } - } else { - context = (context: any)._context; } } const newProps = workInProgress.pendingProps; @@ -3869,7 +3857,12 @@ function attemptEarlyBailoutIfNoScheduledUpdate( break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; - const context: ReactContext = workInProgress.type._context; + let context: ReactContext; + if (enableRenderableContext) { + context = workInProgress.type; + } else { + context = workInProgress.type._context; + } 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..639cec6cbf0d5 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -39,6 +39,7 @@ import { enableCache, enableTransitionTracing, enableFloat, + enableRenderableContext, passChildrenWhenCloningPersistedNodes, } from 'shared/ReactFeatureFlags'; @@ -1505,7 +1506,12 @@ function completeWork( return null; case ContextProvider: // Pop provider fiber - const context: ReactContext = workInProgress.type._context; + let context: ReactContext; + if (enableRenderableContext) { + context = workInProgress.type; + } else { + context = workInProgress.type._context; + } 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..5d9b165635a1e 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, @@ -46,6 +46,7 @@ import { enableLazyContextPropagation, enableFormActions, enableAsyncActions, + enableRenderableContext, } from 'shared/ReactFeatureFlags'; import { getHostTransitionProvider, @@ -561,8 +562,12 @@ function propagateParentContextChanges( const oldProps = currentParent.memoizedProps; if (oldProps !== null) { - const providerType: ReactProviderType = parent.type; - const context: ReactContext = providerType._context; + let context: ReactContext; + if (enableRenderableContext) { + context = parent.type; + } else { + context = parent.type._context; + } 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..0cb1c62ba8d02 100644 --- a/packages/react-reconciler/src/ReactFiberScope.js +++ b/packages/react-reconciler/src/ReactFiberScope.js @@ -22,7 +22,10 @@ import { import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection'; import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags'; -import {enableScopeAPI} from 'shared/ReactFeatureFlags'; +import { + enableScopeAPI, + enableRenderableContext, +} from 'shared/ReactFeatureFlags'; function getSuspenseFallbackChild(fiber: Fiber): Fiber | null { return ((((fiber.child: any): Fiber).sibling: any): Fiber).child; @@ -113,7 +116,10 @@ function collectNearestContextValues( context: ReactContext, childContextValues: Array, ): void { - if (node.tag === ContextProvider && node.type._context === context) { + if ( + node.tag === ContextProvider && + (enableRenderableContext ? node.type : node.type._context) === 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..cc77647cb8556 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -35,6 +35,7 @@ import { enableProfilerTimer, enableCache, enableTransitionTracing, + enableRenderableContext, } from 'shared/ReactFeatureFlags'; import {popHostContainer, popHostContext} from './ReactFiberHostContext'; @@ -160,7 +161,12 @@ function unwindWork( popHostContainer(workInProgress); return null; case ContextProvider: - const context: ReactContext = workInProgress.type._context; + let context: ReactContext; + if (enableRenderableContext) { + context = workInProgress.type; + } else { + context = workInProgress.type._context; + } popProvider(context, workInProgress); return null; case OffscreenComponent: @@ -250,7 +256,12 @@ function unwindInterruptedWork( popSuspenseListContext(interruptedWork); break; case ContextProvider: - const context: ReactContext = interruptedWork.type._context; + let context: ReactContext; + if (enableRenderableContext) { + context = interruptedWork.type; + } else { + context = interruptedWork.type._context; + } 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 ad5eba5150ee0..a6cabd6c641f7 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -1339,6 +1339,7 @@ describe('ReactNewContext', () => { ); }); + // @gate enableRenderableContext || !__DEV__ it('warns when passed a consumer', async () => { const Context = React.createContext(0); function Foo() { @@ -1346,21 +1347,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?', ); }); @@ -1649,99 +1636,23 @@ 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 () => { + // @gate enableRenderableContext + 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..1a8464835ce4f 100644 --- a/packages/react-reconciler/src/getComponentNameFromFiber.js +++ b/packages/react-reconciler/src/getComponentNameFromFiber.js @@ -7,10 +7,13 @@ * @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'; +import { + enableLegacyHidden, + enableRenderableContext, +} from 'shared/ReactFeatureFlags'; import { FunctionComponent, @@ -68,11 +71,21 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null { case CacheComponent: return 'Cache'; case ContextConsumer: - const context: ReactContext = (type: any); - return getContextName(context) + '.Consumer'; + if (enableRenderableContext) { + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; + } else { + const context: ReactContext = (type: any); + return getContextName(context) + '.Consumer'; + } case ContextProvider: - const provider: ReactProviderType = (type: any); - return getContextName(provider._context) + '.Provider'; + if (enableRenderableContext) { + const context: ReactContext = (type: any); + return getContextName(context) + '.Provider'; + } else { + const provider = (type: any); + return getContextName(provider._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..a928b2651a934 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, @@ -32,6 +32,7 @@ import type {ContextSnapshot} from './ReactFizzNewContext'; import type {ComponentStackNode} from './ReactFizzComponentStack'; import type {TreeContext} from './ReactFizzTreeContext'; import type {ThenableState} from './ReactFizzThenable'; +import {enableRenderableContext} from 'shared/ReactFeatureFlags'; import { scheduleWork, @@ -129,6 +130,7 @@ import { REACT_MEMO_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, REACT_SCOPE_TYPE, REACT_OFFSCREEN_TYPE, REACT_POSTPONE_TYPE, @@ -1393,7 +1395,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. @@ -1703,31 +1704,6 @@ function renderContextConsumer( context: ReactContext, 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 render = props.children; if (__DEV__) { @@ -1754,10 +1730,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; @@ -1909,12 +1884,37 @@ function renderElement( return; } case REACT_PROVIDER_TYPE: { - renderContextProvider(request, task, keyPath, type, props); - return; + if (!enableRenderableContext) { + const context: ReactContext = (type: any)._context; + renderContextProvider(request, task, keyPath, context, props); + return; + } + // Fall through } case REACT_CONTEXT_TYPE: { - renderContextConsumer(request, task, keyPath, type, props); - return; + if (enableRenderableContext) { + const context = type; + renderContextProvider(request, task, keyPath, context, props); + return; + } else { + let context: ReactContext = (type: any); + if (__DEV__) { + if ((context: any)._context !== undefined) { + context = (context: any)._context; + } + } + renderContextConsumer(request, task, keyPath, context, props); + return; + } + } + case REACT_CONSUMER_TYPE: { + if (enableRenderableContext) { + const context: ReactContext = (type: ReactConsumerType) + ._context; + renderContextConsumer(request, task, keyPath, context, props); + return; + } + // Fall through } case REACT_LAZY_TYPE: { renderLazyComponent(request, task, keyPath, type, props); diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index 90e73ec46d065..24461ebfbb7bf 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -7,10 +7,14 @@ * @flow */ -import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import { + REACT_PROVIDER_TYPE, + REACT_CONSUMER_TYPE, + REACT_CONTEXT_TYPE, +} from 'shared/ReactSymbols'; -import type {ReactProviderType} from 'shared/ReactTypes'; import type {ReactContext} from 'shared/ReactTypes'; +import {enableRenderableContext} from 'shared/ReactFeatureFlags'; export function createContext(defaultValue: T): ReactContext { // TODO: Second argument used to be an optional `calculateChangedBits` @@ -33,96 +37,71 @@ export function createContext(defaultValue: T): ReactContext { Consumer: (null: any), }; - context.Provider = { - $$typeof: REACT_PROVIDER_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, + if (enableRenderableContext) { + context.Provider = context; + context.Consumer = { + $$typeof: REACT_CONSUMER_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; + } else { + (context: any).Provider = { + $$typeof: REACT_PROVIDER_TYPE, + _context: context, + }; + if (__DEV__) { + const Consumer: any = { + $$typeof: REACT_CONTEXT_TYPE, + _context: context, + }; + Object.defineProperties(Consumer, { + Provider: { + get() { + return context.Provider; + }, + set(_Provider: any) { + context.Provider = _Provider; + }, }, - }, - _threadCount: { - get() { - return context._threadCount; + _currentValue: { + get() { + return context._currentValue; + }, + set(_currentValue: T) { + context._currentValue = _currentValue; + }, }, - set(_threadCount: number) { - context._threadCount = _threadCount; + _currentValue2: { + get() { + return context._currentValue2; + }, + set(_currentValue2: T) { + context._currentValue2 = _currentValue2; + }, }, - }, - 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; + _threadCount: { + get() { + return context._threadCount; + }, + set(_threadCount: number) { + context._threadCount = _threadCount; + }, }, - }, - displayName: { - get() { - return context.displayName; + Consumer: { + get() { + return context.Consumer; + }, }, - 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; - } + displayName: { + get() { + return context.displayName; + }, + set(displayName: void | string) {}, }, - }, - }); - // $FlowFixMe[prop-missing]: Flow complains about missing properties because it doesn't understand defineProperty - context.Consumer = Consumer; - } else { - context.Consumer = context; + }); + (context: any).Consumer = Consumer; + } else { + (context: any).Consumer = context; + } } if (__DEV__) { 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 79f1957acc6f3..3f0fc6ad419ca 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -564,22 +564,15 @@ describe('ReactContextValidator', () => { ); }); + // @gate enableRenderableContext || !__DEV__ 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(); @@ -592,13 +585,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', () => { @@ -723,19 +717,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/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 3d8a7cf563bc8..c2ffe2a652692 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -121,6 +121,8 @@ export const passChildrenWhenCloningPersistedNodes = false; export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__; +export const enableRenderableContext = false; + /** * Enables an expiration time for retry lanes to avoid starvation. */ diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 133a84953ea63..6dca3476be945 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -17,7 +17,8 @@ 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_PROVIDER_TYPE: symbol = Symbol.for('react.provider'); // TODO: Delete with enableRenderableContext +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 107a6082f8e58..59f3362e0cf8c 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/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 497a5b6bcf3a6..3990e11a2ac9b 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -66,6 +66,7 @@ export const enableComponentStackLocations = false; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = true; +export const enableRenderableContext = false; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 67b220974b517..d29f699abbbde 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -49,6 +49,7 @@ export const enableComponentStackLocations = false; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; +export const enableRenderableContext = false; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 63025e79fb18b..6577d2a338893 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -49,6 +49,7 @@ export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; +export const enableRenderableContext = false; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 4baed7ddd2b07..fc53c654b16d9 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -51,6 +51,7 @@ export const enableUseEffectEventHook = false; export const enableClientRenderFallbackOnTextMismatch = true; export const enableUseRefAccessWarning = false; export const enableInfiniteRenderLoopDetection = false; +export const enableRenderableContext = false; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 0105b2bed97c1..8e21c3b1a6286 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -49,6 +49,7 @@ export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; +export const enableRenderableContext = false; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 9b15bddded3fd..cd073c6429982 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -29,6 +29,7 @@ export const enableFormActions = __VARIANT__; export const alwaysThrottleRetries = __VARIANT__; export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__; export const enableUseDeferredValueInitialArg = __VARIANT__; +export const enableRenderableContext = __VARIANT__; export const enableRetryLaneExpiration = __VARIANT__; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 3a3f0f86a6719..798134ab70c24 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -37,6 +37,7 @@ export const { syncLaneExpirationMs, transitionLaneExpirationMs, enableInfiniteRenderLoopDetection, + enableRenderableContext, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build. diff --git a/packages/shared/getComponentNameFromType.js b/packages/shared/getComponentNameFromType.js index 310d4adc595dc..fb63ff465a6af 100644 --- a/packages/shared/getComponentNameFromType.js +++ b/packages/shared/getComponentNameFromType.js @@ -8,10 +8,11 @@ */ 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, @@ -26,7 +27,11 @@ import { REACT_TRACING_MARKER_TYPE, } from 'shared/ReactSymbols'; -import {enableTransitionTracing, enableCache} from './ReactFeatureFlags'; +import { + enableTransitionTracing, + enableCache, + enableRenderableContext, +} from './ReactFeatureFlags'; // Keep in sync with react-reconciler/getComponentNameFromFiber function getWrappedName( @@ -98,12 +103,27 @@ export default function getComponentNameFromType(type: mixed): string | null { } } switch (type.$$typeof) { + case REACT_PROVIDER_TYPE: + if (enableRenderableContext) { + return null; + } else { + const provider = (type: any); + return getContextName(provider._context) + '.Provider'; + } 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'; + if (enableRenderableContext) { + return getContextName(context) + '.Provider'; + } else { + return getContextName(context) + '.Consumer'; + } + case REACT_CONSUMER_TYPE: + if (enableRenderableContext) { + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; + } else { + return null; + } 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..aa2092a923dfc 100644 --- a/packages/shared/isValidElementType.js +++ b/packages/shared/isValidElementType.js @@ -9,10 +9,11 @@ import { REACT_CONTEXT_TYPE, + REACT_CONSUMER_TYPE, + REACT_PROVIDER_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, @@ -31,6 +32,7 @@ import { enableTransitionTracing, enableDebugTracing, enableLegacyHidden, + enableRenderableContext, } from './ReactFeatureFlags'; const REACT_CLIENT_REFERENCE: symbol = Symbol.for('react.client.reference'); @@ -61,8 +63,9 @@ 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 || + (!enableRenderableContext && type.$$typeof === REACT_PROVIDER_TYPE) || + (enableRenderableContext && 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