@samitouri / QOS-React-1 / commits / 14fd9630ee

Switch <Context> to mean <Context.Provider> (#28226)

Previously, `<Context>` was equivalent to `<Context.Consumer>`. However, since the introduction of Hooks, the `<Context.Consumer>` API is rarely used. The goal here is to make the common case cleaner: ```js const ThemeContext = createContext('light') function App() { return ( <ThemeContext value="dark"> ... </ThemeContext> ) } function Button() { const theme = use(ThemeContext) // ... } ``` This is technically a breaking change, but we've been warning about rendering `<Context>` 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.

dan committed Feb 13, 2024 at 15:04 UTC 14fd9630ee04387f4361da289393234e2b7d93b6
33 files changed +400 -473
packages/react-client/src/ReactFlightReplyClient.js
+6 -1
@@ -14,10 +14,12 @@ import type {
14 RejectedThenable,
15 ReactCustomFormAction,
16 } from 'shared/ReactTypes';
17 +import {enableRenderableContext} from 'shared/ReactFeatureFlags';
18
19 import {
20 REACT_ELEMENT_TYPE,
21 REACT_LAZY_TYPE,
22 + REACT_CONTEXT_TYPE,
23 REACT_PROVIDER_TYPE,
24 getIteratorFn,
25 } from 'shared/ReactSymbols';
@@ -302,7 +304,10 @@ export function processReply(
304 'React Lazy cannot be passed to Server Functions from the Client.%s',
305 describeObjectForErrorMessage(parent, key),
306 );
305 - } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
307 + } else if (
308 + (value: any).$$typeof ===
309 + (enableRenderableContext ? REACT_CONTEXT_TYPE : REACT_PROVIDER_TYPE)
310 + ) {
311 console.error(
312 'React Context Providers cannot be passed to Server Functions from the Client.%s',
313 describeObjectForErrorMessage(parent, key),
packages/react-debug-tools/src/ReactDebugHooks.js
+5 -3
@@ -10,7 +10,6 @@
10 import type {
11 Awaited,
12 ReactContext,
13 - ReactProviderType,
13 StartTransitionOptions,
14 Usable,
15 Thenable,
@@ -931,8 +930,11 @@ function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
930 let current: null | Fiber = fiber;
931 while (current) {
932 if (current.tag === ContextProvider) {
934 - const providerType: ReactProviderType<any> = current.type;
935 - const context: ReactContext<any> = providerType._context;
933 + let context: ReactContext<any> = current.type;
934 + if ((context: any)._context !== undefined) {
935 + // Support inspection of pre-19+ providers.
936 + context = (context: any)._context;
937 + }
938 if (!contextMap.has(context)) {
939 // Store the current value that we're going to restore later.
940 contextMap.set(context, context._currentValue);
packages/react-devtools-shared/src/backend/ReactSymbols.js
+2
@@ -51,6 +51,8 @@ export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
51 export const PROVIDER_NUMBER = 0xeacd;
52 export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
53
54 +export const CONSUMER_SYMBOL_STRING = 'Symbol(react.consumer)';
55 +
56 export const SCOPE_NUMBER = 0xead7;
57 export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';
58
packages/react-devtools-shared/src/backend/renderer.js
+51 -2
@@ -79,6 +79,7 @@ import {
79 PROVIDER_SYMBOL_STRING,
80 CONTEXT_NUMBER,
81 CONTEXT_SYMBOL_STRING,
82 + CONSUMER_SYMBOL_STRING,
83 STRICT_MODE_NUMBER,
84 STRICT_MODE_SYMBOL_STRING,
85 PROFILER_NUMBER,
@@ -525,6 +526,15 @@ export function getInternalReactConstants(version: string): {
526 case CONTEXT_NUMBER:
527 case CONTEXT_SYMBOL_STRING:
528 case SERVER_CONTEXT_SYMBOL_STRING:
529 + if (
530 + fiber.type._context === undefined &&
531 + fiber.type.Provider === fiber.type
532 + ) {
533 + // In 19+, Context.Provider === Context, so this is a provider.
534 + resolvedContext = fiber.type;
535 + return `${resolvedContext.displayName || 'Context'}.Provider`;
536 + }
537 +
538 // 16.3-16.5 read from "type" because the Consumer is the actual context object.
539 // 16.6+ should read from "type._context" because Consumer can be different (in DEV).
540 // NOTE Keep in sync with inspectElementRaw()
@@ -533,6 +543,10 @@ export function getInternalReactConstants(version: string): {
543 // NOTE: TraceUpdatesBackendManager depends on the name ending in '.Consumer'
544 // If you change the name, figure out a more resilient way to detect it.
545 return `${resolvedContext.displayName || 'Context'}.Consumer`;
546 + case CONSUMER_SYMBOL_STRING:
547 + // 19+
548 + resolvedContext = fiber.type._context;
549 + return `${resolvedContext.displayName || 'Context'}.Consumer`;
550 case STRICT_MODE_NUMBER:
551 case STRICT_MODE_SYMBOL_STRING:
552 return null;
@@ -3178,8 +3192,14 @@ export function attach(
3192 }
3193 }
3194 } else if (
3181 - typeSymbol === CONTEXT_NUMBER ||
3182 - typeSymbol === CONTEXT_SYMBOL_STRING
3195 + // Detect pre-19 Context Consumers
3196 + (typeSymbol === CONTEXT_NUMBER || typeSymbol === CONTEXT_SYMBOL_STRING) &&
3197 + !(
3198 + // In 19+, CONTEXT_SYMBOL_STRING means a Provider instead.
3199 + // It will be handled in a different branch below.
3200 + // Eventually, this entire branch can be removed.
3201 + (type._context === undefined && type.Provider === type)
3202 + )
3203 ) {
3204 // 16.3-16.5 read from "type" because the Consumer is the actual context object.
3205 // 16.6+ should read from "type._context" because Consumer can be different (in DEV).
@@ -3209,6 +3229,35 @@ export function attach(
3229 }
3230 }
3231
3232 + current = current.return;
3233 + }
3234 + } else if (
3235 + // Detect 19+ Context Consumers
3236 + typeSymbol === CONSUMER_SYMBOL_STRING
3237 + ) {
3238 + // This branch is 19+ only, where Context.Provider === Context.
3239 + // NOTE Keep in sync with getDisplayNameForFiber()
3240 + const consumerResolvedContext = type._context;
3241 +
3242 + // Global context value.
3243 + context = consumerResolvedContext._currentValue || null;
3244 +
3245 + // Look for overridden value.
3246 + let current = ((fiber: any): Fiber).return;
3247 + while (current !== null) {
3248 + const currentType = current.type;
3249 + const currentTypeSymbol = getTypeSymbol(currentType);
3250 + if (
3251 + // In 19+, these are Context Providers
3252 + currentTypeSymbol === CONTEXT_SYMBOL_STRING
3253 + ) {
3254 + const providerResolvedContext = currentType;
3255 + if (providerResolvedContext === consumerResolvedContext) {
3256 + context = current.memoizedProps.value;
3257 + break;
3258 + }
3259 + }
3260 +
3261 current = current.return;
3262 }
3263 }
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
+26 -107
@@ -31,8 +31,7 @@ function initModules() {
31 };
32 }
33
34 -const {resetModules, itRenders, clientRenderOnBadMarkup} =
35 - ReactDOMServerIntegrationUtils(initModules);
34 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
35
36 describe('ReactDOMServerIntegration', () => {
37 beforeEach(() => {
@@ -296,115 +295,35 @@ describe('ReactDOMServerIntegration', () => {
295 expect(e.querySelector('#language3').textContent).toBe('french');
296 });
297
299 - itRenders(
300 - 'should warn with an error message when using Context as consumer in DEV',
301 - async render => {
302 - const Theme = React.createContext('dark');
303 - const Language = React.createContext('french');
298 + itRenders('should treat Context as Context.Provider', async render => {
299 + // The `itRenders` helpers don't work with the gate pragma, so we have to do
300 + // this instead.
301 + if (gate(flags => !flags.enableRenderableContext)) {
302 + return;
303 + }
304
305 - const App = () => (
306 - <div>
307 - <Theme.Provider value="light">
308 - <Language.Provider value="english">
309 - <Theme.Provider value="dark">
310 - <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
311 - </Theme.Provider>
312 - </Language.Provider>
313 - </Theme.Provider>
314 - </div>
315 - );
316 - // We expect 1 error.
317 - await render(<App />, 1);
318 - },
319 - );
320 -
321 - // False positive regression test.
322 - itRenders(
323 - 'should not warn when using Consumer from React < 16.6 with newer renderer',
324 - async render => {
325 - const Theme = React.createContext('dark');
326 - const Language = React.createContext('french');
327 - // React 16.5 and earlier didn't have a separate object.
328 - Theme.Consumer = Theme;
329 -
330 - const App = () => (
331 - <div>
332 - <Theme.Provider value="light">
333 - <Language.Provider value="english">
334 - <Theme.Provider value="dark">
335 - <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
336 - </Theme.Provider>
337 - </Language.Provider>
338 - </Theme.Provider>
339 - </div>
340 - );
341 - // We expect 0 errors.
342 - await render(<App />, 0);
343 - },
344 - );
345 -
346 - itRenders(
347 - 'should warn with an error message when using nested context consumers in DEV',
348 - async render => {
349 - const App = () => {
350 - const Theme = React.createContext('dark');
351 - const Language = React.createContext('french');
305 + const Theme = React.createContext('dark');
306 + const Language = React.createContext('french');
307
353 - return (
354 - <div>
355 - <Theme.Provider value="light">
356 - <Language.Provider value="english">
357 - <Theme.Provider value="dark">
358 - <Theme.Consumer.Consumer>
359 - {theme => <div id="theme1">{theme}</div>}
360 - </Theme.Consumer.Consumer>
361 - </Theme.Provider>
362 - </Language.Provider>
363 - </Theme.Provider>
364 - </div>
365 - );
366 - };
367 - await render(
368 - <App />,
369 - render === clientRenderOnBadMarkup
370 - ? // On hydration mismatch we retry and therefore log the warning again.
371 - 2
372 - : 1,
373 - );
374 - },
375 - );
308 + expect(Theme.Provider).toBe(Theme);
309
377 - itRenders(
378 - 'should warn with an error message when using Context.Consumer.Provider DEV',
379 - async render => {
380 - const App = () => {
381 - const Theme = React.createContext('dark');
382 - const Language = React.createContext('french');
310 + const App = () => (
311 + <div>
312 + <Theme value="light">
313 + <Language value="english">
314 + <Theme value="dark">
315 + <Theme.Consumer>
316 + {theme => <div id="theme1">{theme}</div>}
317 + </Theme.Consumer>
318 + </Theme>
319 + </Language>
320 + </Theme>
321 + </div>
322 + );
323
384 - return (
385 - <div>
386 - <Theme.Provider value="light">
387 - <Language.Provider value="english">
388 - <Theme.Consumer.Provider value="dark">
389 - <Theme.Consumer>
390 - {theme => <div id="theme1">{theme}</div>}
391 - </Theme.Consumer>
392 - </Theme.Consumer.Provider>
393 - </Language.Provider>
394 - </Theme.Provider>
395 - </div>
396 - );
397 - };
398 -
399 - await render(
400 - <App />,
401 - render === clientRenderOnBadMarkup
402 - ? // On hydration mismatch we retry and therefore log the warning again.
403 - 2
404 - : 1,
405 - );
406 - },
407 - );
324 + const e = await render(<App />, 0);
325 + expect(e.textContent).toBe('dark');
326 + });
327
328 it('does not pollute parallel node streams', () => {
329 const LoggedInUser = React.createContext();
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+9 -15
@@ -1000,22 +1000,15 @@ describe('ReactDOMServer', () => {
1000 ]);
1001 });
1002
1003 + // @gate enableRenderableContext || !__DEV__
1004 it('should warn if an invalid contextType is defined', () => {
1005 const Context = React.createContext();
1005 -
1006 class ComponentA extends React.Component {
1007 - // It should warn for both Context.Consumer and Context.Provider
1007 static contextType = Context.Consumer;
1008 render() {
1009 return <div />;
1010 }
1011 }
1013 - class ComponentB extends React.Component {
1014 - static contextType = Context.Provider;
1015 - render() {
1016 - return <div />;
1017 - }
1018 - }
1012
1013 expect(() => {
1014 ReactDOMServer.renderToString(<ComponentA />);
@@ -1028,13 +1021,14 @@ describe('ReactDOMServer', () => {
1021 // Warnings should be deduped by component type
1022 ReactDOMServer.renderToString(<ComponentA />);
1023
1031 - expect(() => {
1032 - ReactDOMServer.renderToString(<ComponentB />);
1033 - }).toErrorDev(
1034 - 'Warning: ComponentB defines an invalid contextType. ' +
1035 - 'contextType should point to the Context object returned by React.createContext(). ' +
1036 - 'Did you accidentally pass the Context.Provider instead?',
1037 - );
1024 + class ComponentB extends React.Component {
1025 + static contextType = Context.Provider;
1026 + render() {
1027 + return <div />;
1028 + }
1029 + }
1030 + // Does not warn because Context === Context.Provider.
1031 + ReactDOMServer.renderToString(<ComponentB />);
1032 });
1033
1034 it('should not warn when class contextType is null', () => {
packages/react-is/src/ReactIs.js
+28 -5
@@ -19,11 +19,13 @@ import {
19 REACT_PORTAL_TYPE,
20 REACT_PROFILER_TYPE,
21 REACT_PROVIDER_TYPE,
22 + REACT_CONSUMER_TYPE,
23 REACT_STRICT_MODE_TYPE,
24 REACT_SUSPENSE_TYPE,
25 REACT_SUSPENSE_LIST_TYPE,
26 } from 'shared/ReactSymbols';
27 import isValidElementType from 'shared/isValidElementType';
28 +import {enableRenderableContext} from 'shared/ReactFeatureFlags';
29
30 export function typeOf(object: any): mixed {
31 if (typeof object === 'object' && object !== null) {
@@ -47,8 +49,17 @@ export function typeOf(object: any): mixed {
49 case REACT_FORWARD_REF_TYPE:
50 case REACT_LAZY_TYPE:
51 case REACT_MEMO_TYPE:
50 - case REACT_PROVIDER_TYPE:
52 return $$typeofType;
53 + case REACT_CONSUMER_TYPE:
54 + if (enableRenderableContext) {
55 + return $$typeofType;
56 + }
57 + // Fall through
58 + case REACT_PROVIDER_TYPE:
59 + if (!enableRenderableContext) {
60 + return $$typeofType;
61 + }
62 + // Fall through
63 default:
64 return $$typeof;
65 }
@@ -61,8 +72,12 @@ export function typeOf(object: any): mixed {
72 return undefined;
73 }
74
64 -export const ContextConsumer = REACT_CONTEXT_TYPE;
65 -export const ContextProvider = REACT_PROVIDER_TYPE;
75 +export const ContextConsumer: symbol = enableRenderableContext
76 + ? REACT_CONSUMER_TYPE
77 + : REACT_CONTEXT_TYPE;
78 +export const ContextProvider: symbol = enableRenderableContext
79 + ? REACT_CONTEXT_TYPE
80 + : REACT_PROVIDER_TYPE;
81 export const Element = REACT_ELEMENT_TYPE;
82 export const ForwardRef = REACT_FORWARD_REF_TYPE;
83 export const Fragment = REACT_FRAGMENT_TYPE;
@@ -77,10 +92,18 @@ export const SuspenseList = REACT_SUSPENSE_LIST_TYPE;
92 export {isValidElementType};
93
94 export function isContextConsumer(object: any): boolean {
80 - return typeOf(object) === REACT_CONTEXT_TYPE;
95 + if (enableRenderableContext) {
96 + return typeOf(object) === REACT_CONSUMER_TYPE;
97 + } else {
98 + return typeOf(object) === REACT_CONTEXT_TYPE;
99 + }
100 }
101 export function isContextProvider(object: any): boolean {
83 - return typeOf(object) === REACT_PROVIDER_TYPE;
102 + if (enableRenderableContext) {
103 + return typeOf(object) === REACT_CONTEXT_TYPE;
104 + } else {
105 + return typeOf(object) === REACT_PROVIDER_TYPE;
106 + }
107 }
108 export function isElement(object: any): boolean {
109 return (
packages/react-reconciler/src/ReactFiber.js
+20 -5
@@ -38,6 +38,7 @@ import {
38 enableDebugTracing,
39 enableFloat,
40 enableDO_NOT_USE_disableStrictPassiveEffect,
41 + enableRenderableContext,
42 } from 'shared/ReactFeatureFlags';
43 import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
44 import {ConcurrentRoot} from './ReactRootTags';
@@ -96,6 +97,7 @@ import {
97 REACT_PROFILER_TYPE,
98 REACT_PROVIDER_TYPE,
99 REACT_CONTEXT_TYPE,
100 + REACT_CONSUMER_TYPE,
101 REACT_SUSPENSE_TYPE,
102 REACT_SUSPENSE_LIST_TYPE,
103 REACT_MEMO_TYPE,
@@ -580,12 +582,25 @@ export function createFiberFromTypeAndProps(
582 if (typeof type === 'object' && type !== null) {
583 switch (type.$$typeof) {
584 case REACT_PROVIDER_TYPE:
583 - fiberTag = ContextProvider;
584 - break getTag;
585 + if (!enableRenderableContext) {
586 + fiberTag = ContextProvider;
587 + break getTag;
588 + }
589 + // Fall through
590 case REACT_CONTEXT_TYPE:
586 - // This is a consumer
587 - fiberTag = ContextConsumer;
588 - break getTag;
591 + if (enableRenderableContext) {
592 + fiberTag = ContextProvider;
593 + break getTag;
594 + } else {
595 + fiberTag = ContextConsumer;
596 + break getTag;
597 + }
598 + case REACT_CONSUMER_TYPE:
599 + if (enableRenderableContext) {
600 + fiberTag = ContextConsumer;
601 + break getTag;
602 + }
603 + // Fall through
604 case REACT_FORWARD_REF_TYPE:
605 fiberTag = ForwardRef;
606 if (__DEV__) {
packages/react-reconciler/src/ReactFiberBeginWork.js
+23 -30
@@ -8,7 +8,7 @@
8 */
9
10 import type {
11 - ReactProviderType,
11 + ReactConsumerType,
12 ReactContext,
13 ReactNodeList,
14 } from 'shared/ReactTypes';
@@ -110,6 +110,7 @@ import {
110 enableFormActions,
111 enableAsyncActions,
112 enablePostpone,
113 + enableRenderableContext,
114 } from 'shared/ReactFeatureFlags';
115 import isArray from 'shared/isArray';
116 import shallowEqual from 'shared/shallowEqual';
@@ -3528,9 +3529,12 @@ function updateContextProvider(
3529 workInProgress: Fiber,
3530 renderLanes: Lanes,
3531 ) {
3531 - const providerType: ReactProviderType<any> = workInProgress.type;
3532 - const context: ReactContext<any> = providerType._context;
3533 -
3532 + let context: ReactContext<any>;
3533 + if (enableRenderableContext) {
3534 + context = workInProgress.type;
3535 + } else {
3536 + context = workInProgress.type._context;
3537 + }
3538 const newProps = workInProgress.pendingProps;
3539 const oldProps = workInProgress.memoizedProps;
3540
@@ -3587,37 +3591,21 @@ function updateContextProvider(
3591 return workInProgress.child;
3592 }
3593
3590 -let hasWarnedAboutUsingContextAsConsumer = false;
3591 -
3594 function updateContextConsumer(
3595 current: Fiber | null,
3596 workInProgress: Fiber,
3597 renderLanes: Lanes,
3598 ) {
3597 - let context: ReactContext<any> = workInProgress.type;
3598 - // The logic below for Context differs depending on PROD or DEV mode. In
3599 - // DEV mode, we create a separate object for Context.Consumer that acts
3600 - // like a proxy to Context. This proxy object adds unnecessary code in PROD
3601 - // so we use the old behaviour (Context.Consumer references Context) to
3602 - // reduce size and overhead. The separate object references context via
3603 - // a property called "_context", which also gives us the ability to check
3604 - // in DEV mode if this property exists or not and warn if it does not.
3605 - if (__DEV__) {
3606 - if ((context: any)._context === undefined) {
3607 - // This may be because it's a Context (rather than a Consumer).
3608 - // Or it may be because it's older React where they're the same thing.
3609 - // We only want to warn if we're sure it's a new React.
3610 - if (context !== context.Consumer) {
3611 - if (!hasWarnedAboutUsingContextAsConsumer) {
3612 - hasWarnedAboutUsingContextAsConsumer = true;
3613 - console.error(
3614 - 'Rendering <Context> directly is not supported and will be removed in ' +
3615 - 'a future major release. Did you mean to render <Context.Consumer> instead?',
3616 - );
3617 - }
3599 + let context: ReactContext<any>;
3600 + if (enableRenderableContext) {
3601 + const consumerType: ReactConsumerType<any> = workInProgress.type;
3602 + context = consumerType._context;
3603 + } else {
3604 + context = workInProgress.type;
3605 + if (__DEV__) {
3606 + if ((context: any)._context !== undefined) {
3607 + context = (context: any)._context;
3608 }
3619 - } else {
3620 - context = (context: any)._context;
3609 }
3610 }
3611 const newProps = workInProgress.pendingProps;
@@ -3869,7 +3857,12 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3857 break;
3858 case ContextProvider: {
3859 const newValue = workInProgress.memoizedProps.value;
3872 - const context: ReactContext<any> = workInProgress.type._context;
3860 + let context: ReactContext<any>;
3861 + if (enableRenderableContext) {
3862 + context = workInProgress.type;
3863 + } else {
3864 + context = workInProgress.type._context;
3865 + }
3866 pushProvider(workInProgress, context, newValue);
3867 break;
3868 }
packages/react-reconciler/src/ReactFiberClassComponent.js
+3 -7
@@ -32,7 +32,7 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
32 import getComponentNameFromType from 'shared/getComponentNameFromType';
33 import assign from 'shared/assign';
34 import isArray from 'shared/isArray';
35 -import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
35 +import {REACT_CONTEXT_TYPE, REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
36
37 import {resolveDefaultProps} from './ReactFiberLazyComponent';
38 import {
@@ -596,8 +596,7 @@ function constructClassInstance(
596 // Allow null for conditional declaration
597 contextType === null ||
598 (contextType !== undefined &&
599 - contextType.$$typeof === REACT_CONTEXT_TYPE &&
600 - contextType._context === undefined); // Not a <Context.Consumer>
599 + contextType.$$typeof === REACT_CONTEXT_TYPE);
600
601 if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
602 didWarnAboutInvalidateContextType.add(ctor);
@@ -611,10 +610,7 @@ function constructClassInstance(
610 'try moving the createContext() call to a separate file.';
611 } else if (typeof contextType !== 'object') {
612 addendum = ' However, it is set to a ' + typeof contextType + '.';
614 - } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
615 - addendum = ' Did you accidentally pass the Context.Provider instead?';
616 - } else if (contextType._context !== undefined) {
617 - // <Context.Consumer>
613 + } else if (contextType.$$typeof === REACT_CONSUMER_TYPE) {
614 addendum = ' Did you accidentally pass the Context.Consumer instead?';
615 } else {
616 addendum =
packages/react-reconciler/src/ReactFiberCompleteWork.js
+7 -1
@@ -39,6 +39,7 @@ import {
39 enableCache,
40 enableTransitionTracing,
41 enableFloat,
42 + enableRenderableContext,
43 passChildrenWhenCloningPersistedNodes,
44 } from 'shared/ReactFeatureFlags';
45
@@ -1505,7 +1506,12 @@ function completeWork(
1506 return null;
1507 case ContextProvider:
1508 // Pop provider fiber
1508 - const context: ReactContext<any> = workInProgress.type._context;
1509 + let context: ReactContext<any>;
1510 + if (enableRenderableContext) {
1511 + context = workInProgress.type;
1512 + } else {
1513 + context = workInProgress.type._context;
1514 + }
1515 popProvider(context, workInProgress);
1516 bubbleProperties(workInProgress);
1517 return null;
packages/react-reconciler/src/ReactFiberNewContext.js
+8 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
10 +import type {ReactContext} from 'shared/ReactTypes';
11 import type {
12 Fiber,
13 ContextDependency,
@@ -46,6 +46,7 @@ import {
46 enableLazyContextPropagation,
47 enableFormActions,
48 enableAsyncActions,
49 + enableRenderableContext,
50 } from 'shared/ReactFeatureFlags';
51 import {
52 getHostTransitionProvider,
@@ -561,8 +562,12 @@ function propagateParentContextChanges(
562
563 const oldProps = currentParent.memoizedProps;
564 if (oldProps !== null) {
564 - const providerType: ReactProviderType<any> = parent.type;
565 - const context: ReactContext<any> = providerType._context;
565 + let context: ReactContext<any>;
566 + if (enableRenderableContext) {
567 + context = parent.type;
568 + } else {
569 + context = parent.type._context;
570 + }
571
572 const newProps = parent.pendingProps;
573 const newValue = newProps.value;
packages/react-reconciler/src/ReactFiberScope.js
+8 -2
@@ -22,7 +22,10 @@ import {
22 import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection';
23
24 import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags';
25 -import {enableScopeAPI} from 'shared/ReactFeatureFlags';
25 +import {
26 + enableScopeAPI,
27 + enableRenderableContext,
28 +} from 'shared/ReactFeatureFlags';
29
30 function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
31 return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
@@ -113,7 +116,10 @@ function collectNearestContextValues<T>(
116 context: ReactContext<T>,
117 childContextValues: Array<T>,
118 ): void {
116 - if (node.tag === ContextProvider && node.type._context === context) {
119 + if (
120 + node.tag === ContextProvider &&
121 + (enableRenderableContext ? node.type : node.type._context) === context
122 + ) {
123 const contextValue = node.memoizedProps.value;
124 childContextValues.push(contextValue);
125 } else {
packages/react-reconciler/src/ReactFiberUnwindWork.js
+13 -2
@@ -35,6 +35,7 @@ import {
35 enableProfilerTimer,
36 enableCache,
37 enableTransitionTracing,
38 + enableRenderableContext,
39 } from 'shared/ReactFeatureFlags';
40
41 import {popHostContainer, popHostContext} from './ReactFiberHostContext';
@@ -160,7 +161,12 @@ function unwindWork(
161 popHostContainer(workInProgress);
162 return null;
163 case ContextProvider:
163 - const context: ReactContext<any> = workInProgress.type._context;
164 + let context: ReactContext<any>;
165 + if (enableRenderableContext) {
166 + context = workInProgress.type;
167 + } else {
168 + context = workInProgress.type._context;
169 + }
170 popProvider(context, workInProgress);
171 return null;
172 case OffscreenComponent:
@@ -250,7 +256,12 @@ function unwindInterruptedWork(
256 popSuspenseListContext(interruptedWork);
257 break;
258 case ContextProvider:
253 - const context: ReactContext<any> = interruptedWork.type._context;
259 + let context: ReactContext<any>;
260 + if (enableRenderableContext) {
261 + context = interruptedWork.type;
262 + } else {
263 + context = interruptedWork.type._context;
264 + }
265 popProvider(context, interruptedWork);
266 break;
267 case OffscreenComponent:
packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+11 -100
@@ -1339,6 +1339,7 @@ describe('ReactNewContext', () => {
1339 );
1340 });
1341
1342 + // @gate enableRenderableContext || !__DEV__
1343 it('warns when passed a consumer', async () => {
1344 const Context = React.createContext(0);
1345 function Foo() {
@@ -1346,21 +1347,7 @@ describe('ReactNewContext', () => {
1347 }
1348 ReactNoop.render(<Foo />);
1349 await expect(async () => await waitForAll([])).toErrorDev(
1349 - 'Calling useContext(Context.Consumer) is not supported, may cause bugs, ' +
1350 - 'and will be removed in a future major release. ' +
1351 - 'Did you mean to call useContext(Context) instead?',
1352 - );
1353 - });
1354 -
1355 - it('warns when passed a provider', async () => {
1356 - const Context = React.createContext(0);
1357 - function Foo() {
1358 - useContext(Context.Provider);
1359 - return null;
1360 - }
1361 - ReactNoop.render(<Foo />);
1362 - await expect(async () => await waitForAll([])).toErrorDev(
1363 - 'Calling useContext(Context.Provider) is not supported. ' +
1350 + 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
1351 'Did you mean to call useContext(Context) instead?',
1352 );
1353 });
@@ -1649,99 +1636,23 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1636 });
1637 });
1638
1652 - it('should warn with an error message when using context as a consumer in DEV', async () => {
1653 - const BarContext = React.createContext({value: 'bar-initial'});
1654 - const BarConsumer = BarContext;
1655 -
1656 - function Component() {
1657 - return (
1658 - <>
1659 - <BarContext.Provider value={{value: 'bar-updated'}}>
1660 - <BarConsumer>
1661 - {({value}) => <div actual={value} expected="bar-updated" />}
1662 - </BarConsumer>
1663 - </BarContext.Provider>
1664 - </>
1665 - );
1666 - }
1667 -
1668 - await expect(async () => {
1669 - ReactNoop.render(<Component />);
1670 - await waitForAll([]);
1671 - }).toErrorDev(
1672 - 'Rendering <Context> directly is not supported and will be removed in ' +
1673 - 'a future major release. Did you mean to render <Context.Consumer> instead?',
1674 - );
1675 - });
1676 -
1677 - // False positive regression test.
1678 - it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => {
1639 + // @gate enableRenderableContext
1640 + it('should treat Context as Context.Provider', async () => {
1641 const BarContext = React.createContext({value: 'bar-initial'});
1680 - // React 16.5 and earlier didn't have a separate object.
1681 - BarContext.Consumer = BarContext;
1642 + expect(BarContext.Provider).toBe(BarContext);
1643
1644 function Component() {
1645 return (
1685 - <>
1686 - <BarContext.Provider value={{value: 'bar-updated'}}>
1687 - <BarContext.Consumer>
1688 - {({value}) => <div actual={value} expected="bar-updated" />}
1689 - </BarContext.Consumer>
1690 - </BarContext.Provider>
1691 - </>
1646 + <BarContext value={{value: 'bar-updated'}}>
1647 + <BarContext.Consumer>
1648 + {({value}) => <span prop={value} />}
1649 + </BarContext.Consumer>
1650 + </BarContext>
1651 );
1652 }
1653
1654 ReactNoop.render(<Component />);
1655 await waitForAll([]);
1697 - });
1698 -
1699 - it('should warn with an error message when using nested context consumers in DEV', async () => {
1700 - const BarContext = React.createContext({value: 'bar-initial'});
1701 - const BarConsumer = BarContext;
1702 -
1703 - function Component() {
1704 - return (
1705 - <>
1706 - <BarContext.Provider value={{value: 'bar-updated'}}>
1707 - <BarConsumer.Consumer.Consumer>
1708 - {({value}) => <div actual={value} expected="bar-updated" />}
1709 - </BarConsumer.Consumer.Consumer>
1710 - </BarContext.Provider>
1711 - </>
1712 - );
1713 - }
1714 -
1715 - await expect(async () => {
1716 - ReactNoop.render(<Component />);
1717 - await waitForAll([]);
1718 - }).toErrorDev(
1719 - 'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
1720 - 'a future major release. Did you mean to render <Context.Consumer> instead?',
1721 - );
1722 - });
1723 -
1724 - it('should warn with an error message when using Context.Consumer.Provider DEV', async () => {
1725 - const BarContext = React.createContext({value: 'bar-initial'});
1726 -
1727 - function Component() {
1728 - return (
1729 - <>
1730 - <BarContext.Consumer.Provider value={{value: 'bar-updated'}}>
1731 - <BarContext.Consumer>
1732 - {({value}) => <div actual={value} expected="bar-updated" />}
1733 - </BarContext.Consumer>
1734 - </BarContext.Consumer.Provider>
1735 - </>
1736 - );
1737 - }
1738 -
1739 - await expect(async () => {
1740 - ReactNoop.render(<Component />);
1741 - await waitForAll([]);
1742 - }).toErrorDev(
1743 - 'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
1744 - 'a future major release. Did you mean to render <Context.Provider> instead?',
1745 - );
1656 + expect(ReactNoop).toMatchRenderedOutput(<span prop="bar-updated" />);
1657 });
1658 });
packages/react-reconciler/src/getComponentNameFromFiber.js
+19 -6
@@ -7,10 +7,13 @@
7 * @flow
8 */
9
10 -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
10 +import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes';
11 import type {Fiber} from './ReactInternalTypes';
12
13 -import {enableLegacyHidden} from 'shared/ReactFeatureFlags';
13 +import {
14 + enableLegacyHidden,
15 + enableRenderableContext,
16 +} from 'shared/ReactFeatureFlags';
17
18 import {
19 FunctionComponent,
@@ -68,11 +71,21 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
71 case CacheComponent:
72 return 'Cache';
73 case ContextConsumer:
71 - const context: ReactContext<any> = (type: any);
72 - return getContextName(context) + '.Consumer';
74 + if (enableRenderableContext) {
75 + const consumer: ReactConsumerType<any> = (type: any);
76 + return getContextName(consumer._context) + '.Consumer';
77 + } else {
78 + const context: ReactContext<any> = (type: any);
79 + return getContextName(context) + '.Consumer';
80 + }
81 case ContextProvider:
74 - const provider: ReactProviderType<any> = (type: any);
75 - return getContextName(provider._context) + '.Provider';
82 + if (enableRenderableContext) {
83 + const context: ReactContext<any> = (type: any);
84 + return getContextName(context) + '.Provider';
85 + } else {
86 + const provider = (type: any);
87 + return getContextName(provider._context) + '.Provider';
88 + }
89 case DehydratedFragment:
90 return 'DehydratedFragment';
91 case ForwardRef:
packages/react-server/src/ReactFizzClassComponent.js
+3 -7
@@ -13,7 +13,7 @@ import {readContext} from './ReactFizzNewContext';
13 import {disableLegacyContext} from 'shared/ReactFeatureFlags';
14 import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
15 import getComponentNameFromType from 'shared/getComponentNameFromType';
16 -import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
16 +import {REACT_CONTEXT_TYPE, REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
17 import assign from 'shared/assign';
18 import isArray from 'shared/isArray';
19
@@ -181,8 +181,7 @@ export function constructClassInstance(
181 // Allow null for conditional declaration
182 contextType === null ||
183 (contextType !== undefined &&
184 - contextType.$$typeof === REACT_CONTEXT_TYPE &&
185 - contextType._context === undefined); // Not a <Context.Consumer>
184 + contextType.$$typeof === REACT_CONTEXT_TYPE);
185
186 if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
187 didWarnAboutInvalidateContextType.add(ctor);
@@ -196,10 +195,7 @@ export function constructClassInstance(
195 'try moving the createContext() call to a separate file.';
196 } else if (typeof contextType !== 'object') {
197 addendum = ' However, it is set to a ' + typeof contextType + '.';
199 - } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
200 - addendum = ' Did you accidentally pass the Context.Provider instead?';
201 - } else if (contextType._context !== undefined) {
202 - // <Context.Consumer>
198 + } else if (contextType.$$typeof === REACT_CONSUMER_TYPE) {
199 addendum = ' Did you accidentally pass the Context.Consumer instead?';
200 } else {
201 addendum =
packages/react-server/src/ReactFizzServer.js
+33 -33
@@ -15,7 +15,7 @@ import type {
15 import type {
16 ReactNodeList,
17 ReactContext,
18 - ReactProviderType,
18 + ReactConsumerType,
19 OffscreenMode,
20 Wakeable,
21 Thenable,
@@ -32,6 +32,7 @@ import type {ContextSnapshot} from './ReactFizzNewContext';
32 import type {ComponentStackNode} from './ReactFizzComponentStack';
33 import type {TreeContext} from './ReactFizzTreeContext';
34 import type {ThenableState} from './ReactFizzThenable';
35 +import {enableRenderableContext} from 'shared/ReactFeatureFlags';
36
37 import {
38 scheduleWork,
@@ -129,6 +130,7 @@ import {
130 REACT_MEMO_TYPE,
131 REACT_PROVIDER_TYPE,
132 REACT_CONTEXT_TYPE,
133 + REACT_CONSUMER_TYPE,
134 REACT_SCOPE_TYPE,
135 REACT_OFFSCREEN_TYPE,
136 REACT_POSTPONE_TYPE,
@@ -1393,7 +1395,6 @@ let didWarnAboutReassigningProps = false;
1395 const didWarnAboutDefaultPropsOnFunctionComponent: {[string]: boolean} = {};
1396 let didWarnAboutGenerators = false;
1397 let didWarnAboutMaps = false;
1396 -let hasWarnedAboutUsingContextAsConsumer = false;
1398
1399 // This would typically be a function component but we still support module pattern
1400 // components for some reason.
@@ -1703,31 +1704,6 @@ function renderContextConsumer(
1704 context: ReactContext<any>,
1705 props: Object,
1706 ): void {
1706 - // The logic below for Context differs depending on PROD or DEV mode. In
1707 - // DEV mode, we create a separate object for Context.Consumer that acts
1708 - // like a proxy to Context. This proxy object adds unnecessary code in PROD
1709 - // so we use the old behaviour (Context.Consumer references Context) to
1710 - // reduce size and overhead. The separate object references context via
1711 - // a property called "_context", which also gives us the ability to check
1712 - // in DEV mode if this property exists or not and warn if it does not.
1713 - if (__DEV__) {
1714 - if ((context: any)._context === undefined) {
1715 - // This may be because it's a Context (rather than a Consumer).
1716 - // Or it may be because it's older React where they're the same thing.
1717 - // We only want to warn if we're sure it's a new React.
1718 - if (context !== context.Consumer) {
1719 - if (!hasWarnedAboutUsingContextAsConsumer) {
1720 - hasWarnedAboutUsingContextAsConsumer = true;
1721 - console.error(
1722 - 'Rendering <Context> directly is not supported and will be removed in ' +
1723 - 'a future major release. Did you mean to render <Context.Consumer> instead?',
1724 - );
1725 - }
1726 - }
1727 - } else {
1728 - context = (context: any)._context;
1729 - }
1730 - }
1707 const render = props.children;
1708
1709 if (__DEV__) {
@@ -1754,10 +1730,9 @@ function renderContextProvider(
1730 request: Request,
1731 task: Task,
1732 keyPath: KeyNode,
1757 - type: ReactProviderType<any>,
1733 + context: ReactContext<any>,
1734 props: Object,
1735 ): void {
1760 - const context = type._context;
1736 const value = props.value;
1737 const children = props.children;
1738 let prevSnapshot;
@@ -1909,12 +1884,37 @@ function renderElement(
1884 return;
1885 }
1886 case REACT_PROVIDER_TYPE: {
1912 - renderContextProvider(request, task, keyPath, type, props);
1913 - return;
1887 + if (!enableRenderableContext) {
1888 + const context: ReactContext<any> = (type: any)._context;
1889 + renderContextProvider(request, task, keyPath, context, props);
1890 + return;
1891 + }
1892 + // Fall through
1893 }
1894 case REACT_CONTEXT_TYPE: {
1916 - renderContextConsumer(request, task, keyPath, type, props);
1917 - return;
1895 + if (enableRenderableContext) {
1896 + const context = type;
1897 + renderContextProvider(request, task, keyPath, context, props);
1898 + return;
1899 + } else {
1900 + let context: ReactContext<any> = (type: any);
1901 + if (__DEV__) {
1902 + if ((context: any)._context !== undefined) {
1903 + context = (context: any)._context;
1904 + }
1905 + }
1906 + renderContextConsumer(request, task, keyPath, context, props);
1907 + return;
1908 + }
1909 + }
1910 + case REACT_CONSUMER_TYPE: {
1911 + if (enableRenderableContext) {
1912 + const context: ReactContext<any> = (type: ReactConsumerType<any>)
1913 + ._context;
1914 + renderContextConsumer(request, task, keyPath, context, props);
1915 + return;
1916 + }
1917 + // Fall through
1918 }
1919 case REACT_LAZY_TYPE: {
1920 renderLazyComponent(request, task, keyPath, type, props);
packages/react/src/ReactContext.js
+63 -84
@@ -7,10 +7,14 @@
7 * @flow
8 */
9
10 -import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
10 +import {
11 + REACT_PROVIDER_TYPE,
12 + REACT_CONSUMER_TYPE,
13 + REACT_CONTEXT_TYPE,
14 +} from 'shared/ReactSymbols';
15
12 -import type {ReactProviderType} from 'shared/ReactTypes';
16 import type {ReactContext} from 'shared/ReactTypes';
17 +import {enableRenderableContext} from 'shared/ReactFeatureFlags';
18
19 export function createContext<T>(defaultValue: T): ReactContext<T> {
20 // TODO: Second argument used to be an optional `calculateChangedBits`
@@ -33,96 +37,71 @@ export function createContext<T>(defaultValue: T): ReactContext<T> {
37 Consumer: (null: any),
38 };
39
36 - context.Provider = {
37 - $$typeof: REACT_PROVIDER_TYPE,
38 - _context: context,
39 - };
40 -
41 - let hasWarnedAboutUsingNestedContextConsumers = false;
42 - let hasWarnedAboutUsingConsumerProvider = false;
43 - let hasWarnedAboutDisplayNameOnConsumer = false;
44 -
45 - if (__DEV__) {
46 - // A separate object, but proxies back to the original context object for
47 - // backwards compatibility. It has a different $$typeof, so we can properly
48 - // warn for the incorrect usage of Context as a Consumer.
49 - const Consumer = {
50 - $$typeof: REACT_CONTEXT_TYPE,
40 + if (enableRenderableContext) {
41 + context.Provider = context;
42 + context.Consumer = {
43 + $$typeof: REACT_CONSUMER_TYPE,
44 _context: context,
45 };
53 - // $FlowFixMe[prop-missing]: Flow complains about not setting a value, which is intentional here
54 - Object.defineProperties(Consumer, {
55 - Provider: {
56 - get() {
57 - if (!hasWarnedAboutUsingConsumerProvider) {
58 - hasWarnedAboutUsingConsumerProvider = true;
59 - console.error(
60 - 'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
61 - 'a future major release. Did you mean to render <Context.Provider> instead?',
62 - );
63 - }
64 - return context.Provider;
65 - },
66 - set(_Provider: ReactProviderType<T>) {
67 - context.Provider = _Provider;
68 - },
69 - },
70 - _currentValue: {
71 - get() {
72 - return context._currentValue;
73 - },
74 - set(_currentValue: T) {
75 - context._currentValue = _currentValue;
76 - },
77 - },
78 - _currentValue2: {
79 - get() {
80 - return context._currentValue2;
81 - },
82 - set(_currentValue2: T) {
83 - context._currentValue2 = _currentValue2;
46 + } else {
47 + (context: any).Provider = {
48 + $$typeof: REACT_PROVIDER_TYPE,
49 + _context: context,
50 + };
51 + if (__DEV__) {
52 + const Consumer: any = {
53 + $$typeof: REACT_CONTEXT_TYPE,
54 + _context: context,
55 + };
56 + Object.defineProperties(Consumer, {
57 + Provider: {
58 + get() {
59 + return context.Provider;
60 + },
61 + set(_Provider: any) {
62 + context.Provider = _Provider;
63 + },
64 },
85 - },
86 - _threadCount: {
87 - get() {
88 - return context._threadCount;
65 + _currentValue: {
66 + get() {
67 + return context._currentValue;
68 + },
69 + set(_currentValue: T) {
70 + context._currentValue = _currentValue;
71 + },
72 },
90 - set(_threadCount: number) {
91 - context._threadCount = _threadCount;
73 + _currentValue2: {
74 + get() {
75 + return context._currentValue2;
76 + },
77 + set(_currentValue2: T) {
78 + context._currentValue2 = _currentValue2;
79 + },
80 },
93 - },
94 - Consumer: {
95 - get() {
96 - if (!hasWarnedAboutUsingNestedContextConsumers) {
97 - hasWarnedAboutUsingNestedContextConsumers = true;
98 - console.error(
99 - 'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
100 - 'a future major release. Did you mean to render <Context.Consumer> instead?',
101 - );
102 - }
103 - return context.Consumer;
81 + _threadCount: {
82 + get() {
83 + return context._threadCount;
84 + },
85 + set(_threadCount: number) {
86 + context._threadCount = _threadCount;
87 + },
88 },
105 - },
106 - displayName: {
107 - get() {
108 - return context.displayName;
89 + Consumer: {
90 + get() {
91 + return context.Consumer;
92 + },
93 },
110 - set(displayName: void | string) {
111 - if (!hasWarnedAboutDisplayNameOnConsumer) {
112 - console.warn(
113 - 'Setting `displayName` on Context.Consumer has no effect. ' +
114 - "You should set it directly on the context with Context.displayName = '%s'.",
115 - displayName,
116 - );
117 - hasWarnedAboutDisplayNameOnConsumer = true;
118 - }
94 + displayName: {
95 + get() {
96 + return context.displayName;
97 + },
98 + set(displayName: void | string) {},
99 },
120 - },
121 - });
122 - // $FlowFixMe[prop-missing]: Flow complains about missing properties because it doesn't understand defineProperty
123 - context.Consumer = Consumer;
124 - } else {
125 - context.Consumer = context;
100 + });
101 + (context: any).Consumer = Consumer;
102 + } else {
103 + (context: any).Consumer = context;
104 + }
105 }
106
107 if (__DEV__) {
packages/react/src/ReactHooks.js
+6 -16
@@ -13,6 +13,7 @@ import type {
13 StartTransitionOptions,
14 Usable,
15 } from 'shared/ReactTypes';
16 +import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
17
18 import ReactCurrentDispatcher from './ReactCurrentDispatcher';
19 import ReactCurrentCache from './ReactCurrentCache';
@@ -72,22 +73,11 @@ export function getCacheForType<T>(resourceType: () => T): T {
73 export function useContext<T>(Context: ReactContext<T>): T {
74 const dispatcher = resolveDispatcher();
75 if (__DEV__) {
75 - // TODO: add a more generic warning for invalid values.
76 - if ((Context: any)._context !== undefined) {
77 - const realContext = (Context: any)._context;
78 - // Don't deduplicate because this legitimately causes bugs
79 - // and nobody should be using this in existing code.
80 - if (realContext.Consumer === Context) {
81 - console.error(
82 - 'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' +
83 - 'removed in a future major release. Did you mean to call useContext(Context) instead?',
84 - );
85 - } else if (realContext.Provider === Context) {
86 - console.error(
87 - 'Calling useContext(Context.Provider) is not supported. ' +
88 - 'Did you mean to call useContext(Context) instead?',
89 - );
90 - }
76 + if (Context.$$typeof === REACT_CONSUMER_TYPE) {
77 + console.error(
78 + 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
79 + 'Did you mean to call useContext(Context) instead?',
80 + );
81 }
82 }
83 return dispatcher.useContext(Context);
packages/react/src/__tests__/ReactContextValidator-test.js
+9 -30
@@ -564,22 +564,15 @@ describe('ReactContextValidator', () => {
564 );
565 });
566
567 + // @gate enableRenderableContext || !__DEV__
568 it('should warn if an invalid contextType is defined', () => {
569 const Context = React.createContext();
569 - // This tests that both Context.Consumer and Context.Provider
570 - // warn about invalid contextType.
570 class ComponentA extends React.Component {
571 static contextType = Context.Consumer;
572 render() {
573 return <div />;
574 }
575 }
577 - class ComponentB extends React.Component {
578 - static contextType = Context.Provider;
579 - render() {
580 - return <div />;
581 - }
582 - }
576
577 expect(() => {
578 ReactTestUtils.renderIntoDocument(<ComponentA />);
@@ -592,13 +585,14 @@ describe('ReactContextValidator', () => {
585 // Warnings should be deduped by component type
586 ReactTestUtils.renderIntoDocument(<ComponentA />);
587
595 - expect(() => {
596 - ReactTestUtils.renderIntoDocument(<ComponentB />);
597 - }).toErrorDev(
598 - 'Warning: ComponentB defines an invalid contextType. ' +
599 - 'contextType should point to the Context object returned by React.createContext(). ' +
600 - 'Did you accidentally pass the Context.Provider instead?',
601 - );
588 + class ComponentB extends React.Component {
589 + static contextType = Context.Provider;
590 + render() {
591 + return <div />;
592 + }
593 + }
594 + // This doesn't warn since Context.Provider === Context now.
595 + ReactTestUtils.renderIntoDocument(<ComponentB />);
596 });
597
598 it('should not warn when class contextType is null', () => {
@@ -723,19 +717,4 @@ describe('ReactContextValidator', () => {
717 ' in Validator (at **)',
718 );
719 });
726 -
727 - it('warns if displayName is set on the consumer type', () => {
728 - const Context = React.createContext(null);
729 -
730 - expect(() => {
731 - Context.Consumer.displayName = 'IgnoredName';
732 - }).toWarnDev(
733 - 'Warning: Setting `displayName` on Context.Consumer has no effect. ' +
734 - "You should set it directly on the context with Context.displayName = 'IgnoredName'.",
735 - {withoutStack: true},
736 - );
737 -
738 - // warning is deduped by Context so subsequent setting is fine
739 - Context.Consumer.displayName = 'ADifferentName';
740 - });
720 });
packages/shared/ReactFeatureFlags.js
+2
@@ -121,6 +121,8 @@ export const passChildrenWhenCloningPersistedNodes = false;
121
122 export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
123
124 +export const enableRenderableContext = false;
125 +
126 /**
127 * Enables an expiration time for retry lanes to avoid starvation.
128 */
packages/shared/ReactSymbols.js
+2 -1
@@ -17,7 +17,8 @@ export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal');
17 export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment');
18 export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
19 export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
20 -export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider');
20 +export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider'); // TODO: Delete with enableRenderableContext
21 +export const REACT_CONSUMER_TYPE: symbol = Symbol.for('react.consumer');
22 export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
23 export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
24 export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense');
packages/shared/ReactTypes.js
+5 -5
@@ -25,7 +25,7 @@ export type ReactText = string | number;
25
26 export type ReactProvider<T> = {
27 $$typeof: symbol | number,
28 - type: ReactProviderType<T>,
28 + type: ReactContext<T>,
29 key: null | string,
30 ref: null,
31 props: {
@@ -34,14 +34,14 @@ export type ReactProvider<T> = {
34 },
35 };
36
37 -export type ReactProviderType<T> = {
37 +export type ReactConsumerType<T> = {
38 $$typeof: symbol | number,
39 _context: ReactContext<T>,
40 };
41
42 export type ReactConsumer<T> = {
43 $$typeof: symbol | number,
44 - type: ReactContext<T>,
44 + type: ReactConsumerType<T>,
45 key: null | string,
46 ref: null,
47 props: {
@@ -51,8 +51,8 @@ export type ReactConsumer<T> = {
51
52 export type ReactContext<T> = {
53 $$typeof: symbol | number,
54 - Consumer: ReactContext<T>,
55 - Provider: ReactProviderType<T>,
54 + Consumer: ReactConsumerType<T>,
55 + Provider: ReactContext<T>,
56 _currentValue: T,
57 _currentValue2: T,
58 _threadCount: number,
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -66,6 +66,7 @@ export const enableComponentStackLocations = false;
66 export const enableLegacyFBSupport = false;
67 export const enableFilterEmptyStringAttributesDOM = true;
68 export const enableGetInspectorDataForInstanceInProduction = true;
69 +export const enableRenderableContext = false;
70
71 export const enableRetryLaneExpiration = false;
72 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = false;
49 export const enableLegacyFBSupport = false;
50 export const enableFilterEmptyStringAttributesDOM = true;
51 export const enableGetInspectorDataForInstanceInProduction = false;
52 +export const enableRenderableContext = false;
53
54 export const enableRetryLaneExpiration = false;
55 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = true;
49 export const enableLegacyFBSupport = false;
50 export const enableFilterEmptyStringAttributesDOM = true;
51 export const enableGetInspectorDataForInstanceInProduction = false;
52 +export const enableRenderableContext = false;
53
54 export const enableRetryLaneExpiration = false;
55 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -51,6 +51,7 @@ export const enableUseEffectEventHook = false;
51 export const enableClientRenderFallbackOnTextMismatch = true;
52 export const enableUseRefAccessWarning = false;
53 export const enableInfiniteRenderLoopDetection = false;
54 +export const enableRenderableContext = false;
55
56 export const enableRetryLaneExpiration = false;
57 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = true;
49 export const enableLegacyFBSupport = false;
50 export const enableFilterEmptyStringAttributesDOM = true;
51 export const enableGetInspectorDataForInstanceInProduction = false;
52 +export const enableRenderableContext = false;
53
54 export const enableRetryLaneExpiration = false;
55 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -29,6 +29,7 @@ export const enableFormActions = __VARIANT__;
29 export const alwaysThrottleRetries = __VARIANT__;
30 export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
31 export const enableUseDeferredValueInitialArg = __VARIANT__;
32 +export const enableRenderableContext = __VARIANT__;
33
34 export const enableRetryLaneExpiration = __VARIANT__;
35 export const retryLaneExpirationMs = 5000;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -37,6 +37,7 @@ export const {
37 syncLaneExpirationMs,
38 transitionLaneExpirationMs,
39 enableInfiniteRenderLoopDetection,
40 + enableRenderableContext,
41 } = dynamicFeatureFlags;
42
43 // On WWW, __EXPERIMENTAL__ is used for a new modern build.
packages/shared/getComponentNameFromType.js
+26 -6
@@ -8,10 +8,11 @@
8 */
9
10 import type {LazyComponent} from 'react/src/ReactLazy';
11 -import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
11 +import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes';
12
13 import {
14 REACT_CONTEXT_TYPE,
15 + REACT_CONSUMER_TYPE,
16 REACT_FORWARD_REF_TYPE,
17 REACT_FRAGMENT_TYPE,
18 REACT_PORTAL_TYPE,
@@ -26,7 +27,11 @@ import {
27 REACT_TRACING_MARKER_TYPE,
28 } from 'shared/ReactSymbols';
29
29 -import {enableTransitionTracing, enableCache} from './ReactFeatureFlags';
30 +import {
31 + enableTransitionTracing,
32 + enableCache,
33 + enableRenderableContext,
34 +} from './ReactFeatureFlags';
35
36 // Keep in sync with react-reconciler/getComponentNameFromFiber
37 function getWrappedName(
@@ -98,12 +103,27 @@ export default function getComponentNameFromType(type: mixed): string | null {
103 }
104 }
105 switch (type.$$typeof) {
106 + case REACT_PROVIDER_TYPE:
107 + if (enableRenderableContext) {
108 + return null;
109 + } else {
110 + const provider = (type: any);
111 + return getContextName(provider._context) + '.Provider';
112 + }
113 case REACT_CONTEXT_TYPE:
114 const context: ReactContext<any> = (type: any);
103 - return getContextName(context) + '.Consumer';
104 - case REACT_PROVIDER_TYPE:
105 - const provider: ReactProviderType<any> = (type: any);
106 - return getContextName(provider._context) + '.Provider';
115 + if (enableRenderableContext) {
116 + return getContextName(context) + '.Provider';
117 + } else {
118 + return getContextName(context) + '.Consumer';
119 + }
120 + case REACT_CONSUMER_TYPE:
121 + if (enableRenderableContext) {
122 + const consumer: ReactConsumerType<any> = (type: any);
123 + return getContextName(consumer._context) + '.Consumer';
124 + } else {
125 + return null;
126 + }
127 case REACT_FORWARD_REF_TYPE:
128 return getWrappedName(type, type.render, 'ForwardRef');
129 case REACT_MEMO_TYPE:
packages/shared/isValidElementType.js
+5 -2
@@ -9,10 +9,11 @@
9
10 import {
11 REACT_CONTEXT_TYPE,
12 + REACT_CONSUMER_TYPE,
13 + REACT_PROVIDER_TYPE,
14 REACT_FORWARD_REF_TYPE,
15 REACT_FRAGMENT_TYPE,
16 REACT_PROFILER_TYPE,
15 - REACT_PROVIDER_TYPE,
17 REACT_DEBUG_TRACING_MODE_TYPE,
18 REACT_STRICT_MODE_TYPE,
19 REACT_SUSPENSE_TYPE,
@@ -31,6 +32,7 @@ import {
32 enableTransitionTracing,
33 enableDebugTracing,
34 enableLegacyHidden,
35 + enableRenderableContext,
36 } from './ReactFeatureFlags';
37
38 const REACT_CLIENT_REFERENCE: symbol = Symbol.for('react.client.reference');
@@ -61,8 +63,9 @@ export default function isValidElementType(type: mixed): boolean {
63 if (
64 type.$$typeof === REACT_LAZY_TYPE ||
65 type.$$typeof === REACT_MEMO_TYPE ||
64 - type.$$typeof === REACT_PROVIDER_TYPE ||
66 type.$$typeof === REACT_CONTEXT_TYPE ||
67 + (!enableRenderableContext && type.$$typeof === REACT_PROVIDER_TYPE) ||
68 + (enableRenderableContext && type.$$typeof === REACT_CONSUMER_TYPE) ||
69 type.$$typeof === REACT_FORWARD_REF_TYPE ||
70 // This needs to include all possible module reference object
71 // types supported by any Flight configuration anywhere since