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 8fd639322e46b..c67e9a22d5f00 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'; @@ -3529,9 +3530,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; @@ -3588,37 +3592,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; @@ -3870,7 +3858,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 3e90a2cb09227..9d9678af7ad0f 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 11636ba001a0a..e072c18e59bcb 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