Skip to content

Commit

Permalink
Switch <Context> to mean <Context.Provider> (#28226)
Browse files Browse the repository at this point in the history
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 5696782.** 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.

DiffTrain build for commit 14fd963.
  • Loading branch information
rickhanlonii committed Feb 13, 2024
1 parent a511ad1 commit e7e86e5
Show file tree
Hide file tree
Showing 18 changed files with 522 additions and 435 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<99069a73679761ba23b9ab688e792429>>
* @generated SignedSource<<50de4881bc1e7323607d21cae81406f7>>
*/

"use strict";
Expand All @@ -25,7 +25,9 @@ if (__DEV__) {
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider"); // TODO: Delete with enableRenderableContext

var REACT_CONSUMER_TYPE = Symbol.for("react.consumer");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
Expand All @@ -38,6 +40,7 @@ if (__DEV__) {
// NOTE: There are no flags, currently. Uncomment the stuff below if we add one.
var enableDebugTracing = false;
var enableScopeAPI = false;
var enableRenderableContext = false;
var enableLegacyHidden = false;
var enableTransitionTracing = false;

Expand Down Expand Up @@ -67,8 +70,9 @@ if (__DEV__) {
if (
type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
enableRenderableContext ||
type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
Expand Down Expand Up @@ -107,9 +111,18 @@ if (__DEV__) {
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
case REACT_PROVIDER_TYPE:
return $$typeofType;

case REACT_CONSUMER_TYPE:

// Fall through

case REACT_PROVIDER_TYPE: {
return $$typeofType;
}

// Fall through

default:
return $$typeof;
}
Expand All @@ -135,10 +148,14 @@ if (__DEV__) {
var Suspense = REACT_SUSPENSE_TYPE;
var SuspenseList = REACT_SUSPENSE_LIST_TYPE;
function isContextConsumer(object) {
return typeOf(object) === REACT_CONTEXT_TYPE;
{
return typeOf(object) === REACT_CONTEXT_TYPE;
}
}
function isContextProvider(object) {
return typeOf(object) === REACT_PROVIDER_TYPE;
{
return typeOf(object) === REACT_PROVIDER_TYPE;
}
}
function isElement(object) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<41fcb3aed2a769fa81ed6b7a5c498689>>
* @generated SignedSource<<db70fee50d49f5366bc39dfb803ccafb>>
*/

"use strict";
Expand All @@ -17,6 +17,7 @@ var REACT_ELEMENT_TYPE = Symbol.for("react.element"),
REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"),
REACT_PROFILER_TYPE = Symbol.for("react.profiler"),
REACT_PROVIDER_TYPE = Symbol.for("react.provider"),
REACT_CONSUMER_TYPE = Symbol.for("react.consumer"),
REACT_CONTEXT_TYPE = Symbol.for("react.context"),
REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"),
REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"),
Expand Down Expand Up @@ -44,6 +45,8 @@ function typeOf(object) {
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
return object;
case REACT_CONSUMER_TYPE:
case REACT_PROVIDER_TYPE:
return object;
default:
Expand Down Expand Up @@ -121,8 +124,8 @@ exports.isValidElementType = function (type) {
null !== type &&
(type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
type.$$typeof === REACT_CLIENT_REFERENCE ||
void 0 !== type.getModuleId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<41fcb3aed2a769fa81ed6b7a5c498689>>
* @generated SignedSource<<db70fee50d49f5366bc39dfb803ccafb>>
*/

"use strict";
Expand All @@ -17,6 +17,7 @@ var REACT_ELEMENT_TYPE = Symbol.for("react.element"),
REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"),
REACT_PROFILER_TYPE = Symbol.for("react.profiler"),
REACT_PROVIDER_TYPE = Symbol.for("react.provider"),
REACT_CONSUMER_TYPE = Symbol.for("react.consumer"),
REACT_CONTEXT_TYPE = Symbol.for("react.context"),
REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"),
REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"),
Expand Down Expand Up @@ -44,6 +45,8 @@ function typeOf(object) {
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
return object;
case REACT_CONSUMER_TYPE:
case REACT_PROVIDER_TYPE:
return object;
default:
Expand Down Expand Up @@ -121,8 +124,8 @@ exports.isValidElementType = function (type) {
null !== type &&
(type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
type.$$typeof === REACT_CLIENT_REFERENCE ||
void 0 !== type.getModuleId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<4781c0c583d1e61ede17a8c473948719>>
* @generated SignedSource<<8f7c91d2c5c341803806e7a3db730537>>
*/

"use strict";
Expand Down Expand Up @@ -189,7 +189,9 @@ if (__DEV__) {
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider"); // TODO: Delete with enableRenderableContext

var REACT_CONSUMER_TYPE = Symbol.for("react.consumer");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
Expand Down Expand Up @@ -294,13 +296,21 @@ if (__DEV__) {
}

switch (type.$$typeof) {
case REACT_PROVIDER_TYPE: {
var provider = type;
return getContextName$1(provider._context) + ".Provider";
}

case REACT_CONTEXT_TYPE:
var context = type;
return getContextName$1(context) + ".Consumer";

case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName$1(provider._context) + ".Provider";
{
return getContextName$1(context) + ".Consumer";
}

case REACT_CONSUMER_TYPE: {
return null;
}

case REACT_FORWARD_REF_TYPE:
return getWrappedName$1(type, type.render, "ForwardRef");
Expand Down Expand Up @@ -353,13 +363,15 @@ if (__DEV__) {
case CacheComponent:
return "Cache";

case ContextConsumer:
case ContextConsumer: {
var context = type;
return getContextName(context) + ".Consumer";
}

case ContextProvider:
case ContextProvider: {
var provider = type;
return getContextName(provider._context) + ".Provider";
}

case DehydratedFragment:
return "DehydratedFragment";
Expand Down Expand Up @@ -11446,8 +11458,7 @@ if (__DEV__) {
var isValid = // 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 @@ -11461,11 +11472,7 @@ if (__DEV__) {
"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 {
Expand Down Expand Up @@ -15383,8 +15390,12 @@ if (__DEV__) {
var hasWarnedAboutUsingNoValuePropOnContextProvider = false;

function updateContextProvider(current, workInProgress, renderLanes) {
var providerType = workInProgress.type;
var context = providerType._context;
var context;

{
context = workInProgress.type._context;
}

var newProps = workInProgress.pendingProps;
var oldProps = workInProgress.memoizedProps;
var newValue = newProps.value;
Expand Down Expand Up @@ -15443,34 +15454,16 @@ if (__DEV__) {
return workInProgress.child;
}

var hasWarnedAboutUsingContextAsConsumer = false;

function updateContextConsumer(current, workInProgress, renderLanes) {
var context = 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.
var context;

{
if (context._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;
context = workInProgress.type;

error(
"Rendering <Context> directly is not supported and will be removed in " +
"a future major release. Did you mean to render <Context.Consumer> instead?"
);
}
{
if (context._context !== undefined) {
context = context._context;
}
} else {
context = context._context;
}
}

Expand Down Expand Up @@ -15670,7 +15663,12 @@ if (__DEV__) {

case ContextProvider: {
var newValue = workInProgress.memoizedProps.value;
var context = workInProgress.type._context;
var context;

{
context = workInProgress.type._context;
}

pushProvider(workInProgress, context, newValue);
break;
}
Expand Down Expand Up @@ -17408,7 +17406,12 @@ if (__DEV__) {

case ContextProvider:
// Pop provider fiber
var context = workInProgress.type._context;
var context;

{
context = workInProgress.type._context;
}

popProvider(context, workInProgress);
bubbleProperties(workInProgress);
return null;
Expand Down Expand Up @@ -17849,7 +17852,12 @@ if (__DEV__) {
return null;

case ContextProvider:
var context = workInProgress.type._context;
var context;

{
context = workInProgress.type._context;
}

popProvider(context, workInProgress);
return null;

Expand Down Expand Up @@ -17929,7 +17937,12 @@ if (__DEV__) {
break;

case ContextProvider:
var context = interruptedWork.type._context;
var context;

{
context = interruptedWork.type._context;
}

popProvider(context, interruptedWork);
break;

Expand Down Expand Up @@ -25613,14 +25626,21 @@ if (__DEV__) {
default: {
if (typeof type === "object" && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
case REACT_PROVIDER_TYPE: {
fiberTag = ContextProvider;
break getTag;
}

// Fall through

case REACT_CONTEXT_TYPE:
// This is a consumer
case REACT_CONTEXT_TYPE: {
fiberTag = ContextConsumer;
break getTag;
}

case REACT_CONSUMER_TYPE:

// Fall through

case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
Expand Down Expand Up @@ -25917,7 +25937,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "18.3.0-canary-32df74dba-20240212";
var ReactVersion = "18.3.0-canary-14fd9630e-20240213";

// Might add PROFILE later.

Expand Down
Loading

0 comments on commit e7e86e5

Please sign in to comment.