Skip to content

Commit

Permalink
Switch <Context> to mean <Context.Provider>
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Feb 5, 2024
1 parent 4728548 commit 5696782
Show file tree
Hide file tree
Showing 23 changed files with 112 additions and 480 deletions.
4 changes: 2 additions & 2 deletions packages/react-client/src/ReactFlightReplyClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
import {
REACT_ELEMENT_TYPE,
REACT_LAZY_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
getIteratorFn,
} from 'shared/ReactSymbols';

Expand Down Expand Up @@ -297,7 +297,7 @@ export function processReply(
'React Lazy cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
);
} else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
} else if ((value: any).$$typeof === REACT_CONTEXT_TYPE) {
console.error(
'React Context Providers cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
Expand Down
4 changes: 1 addition & 3 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import type {
Awaited,
ReactContext,
ReactProviderType,
StartTransitionOptions,
} from 'shared/ReactTypes';
import type {
Expand Down Expand Up @@ -797,8 +796,7 @@ function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
let current: null | Fiber = fiber;
while (current) {
if (current.tag === ContextProvider) {
const providerType: ReactProviderType<any> = current.type;
const context: ReactContext<any> = providerType._context;
const context: ReactContext<any> = current.type;
if (!contextMap.has(context)) {
// Store the current value that we're going to restore later.
contextMap.set(context, context._currentValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ function initModules() {
};
}

const {resetModules, itRenders, clientRenderOnBadMarkup} =
ReactDOMServerIntegrationUtils(initModules);
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);

describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
Expand Down Expand Up @@ -298,115 +297,29 @@ describe('ReactDOMServerIntegration', () => {
expect(e.querySelector('#language3').textContent).toBe('french');
});

itRenders(
'should warn with an error message when using Context as consumer in DEV',
async render => {
const Theme = React.createContext('dark');
const Language = React.createContext('french');

const App = () => (
<div>
<Theme.Provider value="light">
<Language.Provider value="english">
<Theme.Provider value="dark">
<Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
</Theme.Provider>
</Language.Provider>
</Theme.Provider>
</div>
);
// We expect 1 error.
await render(<App />, 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 = () => (
<div>
<Theme.Provider value="light">
<Language.Provider value="english">
<Theme.Provider value="dark">
<Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
</Theme.Provider>
</Language.Provider>
</Theme.Provider>
</div>
);
// We expect 0 errors.
await render(<App />, 0);
},
);

itRenders(
'should warn with an error message when using nested context consumers in DEV',
async render => {
const App = () => {
const Theme = React.createContext('dark');
const Language = React.createContext('french');
itRenders('should treat Context as Context.Provider', async render => {
const Theme = React.createContext('dark');
const Language = React.createContext('french');

return (
<div>
<Theme.Provider value="light">
<Language.Provider value="english">
<Theme.Provider value="dark">
<Theme.Consumer.Consumer>
{theme => <div id="theme1">{theme}</div>}
</Theme.Consumer.Consumer>
</Theme.Provider>
</Language.Provider>
</Theme.Provider>
</div>
);
};
await render(
<App />,
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 = () => (
<div>
<Theme value="light">
<Language value="english">
<Theme value="dark">
<Theme.Consumer>
{theme => <div id="theme1">{theme}</div>}
</Theme.Consumer>
</Theme>
</Language>
</Theme>
</div>
);

return (
<div>
<Theme.Provider value="light">
<Language.Provider value="english">
<Theme.Consumer.Provider value="dark">
<Theme.Consumer>
{theme => <div id="theme1">{theme}</div>}
</Theme.Consumer>
</Theme.Consumer.Provider>
</Language.Provider>
</Theme.Provider>
</div>
);
};

await render(
<App />,
render === clientRenderOnBadMarkup
? // On hydration mismatch we retry and therefore log the warning again.
2
: 1,
);
},
);
const e = await render(<App />, 0);
expect(e.textContent).toBe('dark');
});

it('does not pollute parallel node streams', () => {
const LoggedInUser = React.createContext();
Expand Down
23 changes: 8 additions & 15 deletions packages/react-dom/src/__tests__/ReactServerRendering-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,20 +1003,12 @@ describe('ReactDOMServer', () => {

it('should warn if an invalid contextType is defined', () => {
const Context = React.createContext();

class ComponentA extends React.Component {
// It should warn for both Context.Consumer and Context.Provider
static contextType = Context.Consumer;
render() {
return <div />;
}
}
class ComponentB extends React.Component {
static contextType = Context.Provider;
render() {
return <div />;
}
}

expect(() => {
ReactDOMServer.renderToString(<ComponentA />);
Expand All @@ -1029,13 +1021,14 @@ describe('ReactDOMServer', () => {
// Warnings should be deduped by component type
ReactDOMServer.renderToString(<ComponentA />);

expect(() => {
ReactDOMServer.renderToString(<ComponentB />);
}).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 <div />;
}
}
// Does not warn because Context === Context.Provider.
ReactDOMServer.renderToString(<ComponentB />);
});

it('should not warn when class contextType is null', () => {
Expand Down
12 changes: 6 additions & 6 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
REACT_MEMO_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
Expand Down Expand Up @@ -47,7 +47,7 @@ export function typeOf(object: any): mixed {
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
case REACT_PROVIDER_TYPE:
case REACT_CONSUMER_TYPE:
return $$typeofType;
default:
return $$typeof;
Expand All @@ -61,8 +61,8 @@ export function typeOf(object: any): mixed {
return undefined;
}

export const ContextConsumer = REACT_CONTEXT_TYPE;
export const ContextProvider = REACT_PROVIDER_TYPE;
export const ContextConsumer = REACT_CONSUMER_TYPE;
export const ContextProvider = REACT_CONTEXT_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const ForwardRef = REACT_FORWARD_REF_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
Expand Down Expand Up @@ -107,10 +107,10 @@ export function isConcurrentMode(object: any): boolean {
return false;
}
export function isContextConsumer(object: any): boolean {
return typeOf(object) === REACT_CONTEXT_TYPE;
return typeOf(object) === REACT_CONSUMER_TYPE;
}
export function isContextProvider(object: any): boolean {
return typeOf(object) === REACT_PROVIDER_TYPE;
return typeOf(object) === REACT_CONTEXT_TYPE;
}
export function isElement(object: any): boolean {
return (
Expand Down
6 changes: 3 additions & 3 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ import {
REACT_DEBUG_TRACING_MODE_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
REACT_MEMO_TYPE,
Expand Down Expand Up @@ -581,10 +581,10 @@ export function createFiberFromTypeAndProps(
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
case REACT_CONTEXT_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
case REACT_CONSUMER_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
Expand Down
39 changes: 6 additions & 33 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import type {
ReactProviderType,
ReactConsumerType,
ReactContext,
ReactNodeList,
} from 'shared/ReactTypes';
Expand Down Expand Up @@ -3532,9 +3532,7 @@ function updateContextProvider(
workInProgress: Fiber,
renderLanes: Lanes,
) {
const providerType: ReactProviderType<any> = workInProgress.type;
const context: ReactContext<any> = providerType._context;

const context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;

Expand Down Expand Up @@ -3591,39 +3589,14 @@ function updateContextProvider(
return workInProgress.child;
}

let hasWarnedAboutUsingContextAsConsumer = false;

function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any> = 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 <Context> directly is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);
}
}
} else {
context = (context: any)._context;
}
}
const consumerType: ReactConsumerType<any> = workInProgress.type;
const context = consumerType._context;

const newProps = workInProgress.pendingProps;
const render = newProps.children;

Expand Down Expand Up @@ -3869,7 +3842,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
const context: ReactContext<any> = workInProgress.type._context;
const context: ReactContext<any> = workInProgress.type;
pushProvider(workInProgress, context, newValue);
break;
}
Expand Down
10 changes: 3 additions & 7 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 <Context.Consumer>
contextType.$$typeof === REACT_CONTEXT_TYPE);

if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
didWarnAboutInvalidateContextType.add(ctor);
Expand All @@ -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) {
// <Context.Consumer>
} else if (contextType.$$typeof === REACT_CONSUMER_TYPE) {
addendum = ' Did you accidentally pass the Context.Consumer instead?';
} else {
addendum =
Expand Down
Loading

0 comments on commit 5696782

Please sign in to comment.