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 [14fd963](14fd963)
  • Loading branch information
rickhanlonii committed Feb 13, 2024
1 parent 69dc39f commit 1985f4f
Show file tree
Hide file tree
Showing 37 changed files with 4,244 additions and 3,219 deletions.
36 changes: 28 additions & 8 deletions compiled/facebook-www/JSXDEVRuntime-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 @@ -106,8 +108,8 @@ if (__DEV__) {
var dynamicFeatureFlags = require("ReactFeatureFlags");

var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
// On WWW, false is used for a new modern build.
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext; // On WWW, false is used for a new modern build.

var REACT_CLIENT_REFERENCE$2 = Symbol.for("react.client.reference");
function isValidElementType(type) {
Expand Down Expand Up @@ -135,8 +137,9 @@ if (__DEV__) {
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
// we don't know which Flight build this will end up being used
Expand Down Expand Up @@ -231,13 +234,30 @@ if (__DEV__) {
}

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

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

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

case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
var consumer = type;
return getContextName(consumer._context) + ".Consumer";
} else {
return null;
}

case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
Expand Down
36 changes: 28 additions & 8 deletions compiled/facebook-www/JSXDEVRuntime-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 @@ -106,8 +108,8 @@ if (__DEV__) {
var dynamicFeatureFlags = require("ReactFeatureFlags");

var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
// On WWW, true is used for a new modern build.
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext; // On WWW, true is used for a new modern build.

var REACT_CLIENT_REFERENCE$2 = Symbol.for("react.client.reference");
function isValidElementType(type) {
Expand Down Expand Up @@ -135,8 +137,9 @@ if (__DEV__) {
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
// we don't know which Flight build this will end up being used
Expand Down Expand Up @@ -231,13 +234,30 @@ if (__DEV__) {
}

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

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

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

case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
var consumer = type;
return getContextName(consumer._context) + ".Consumer";
} else {
return null;
}

case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8d48183291870898ec42ac1f84482d9d26789424
14fd9630ee04387f4361da289393234e2b7d93b6
194 changes: 88 additions & 106 deletions compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (__DEV__) {
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = "18.3.0-www-classic-ab1e0612";
var ReactVersion = "18.3.0-www-classic-38e30556";

// ATTENTION
// When adding new symbols to this file,
Expand All @@ -35,7 +35,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 @@ -473,8 +475,8 @@ if (__DEV__) {

var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions;
// On WWW, false is used for a new modern build.
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext; // On WWW, false is used for a new modern build.

function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
Expand Down Expand Up @@ -556,13 +558,30 @@ if (__DEV__) {
}

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

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

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

case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
var consumer = type;
return getContextName(consumer._context) + ".Consumer";
} else {
return null;
}

case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
Expand Down Expand Up @@ -1022,8 +1041,9 @@ if (__DEV__) {
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
// we don't know which Flight build this will end up being used
Expand Down Expand Up @@ -2472,98 +2492,71 @@ if (__DEV__) {
Provider: null,
Consumer: null
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
var hasWarnedAboutUsingNestedContextConsumers = false;
var hasWarnedAboutUsingConsumerProvider = false;
var hasWarnedAboutDisplayNameOnConsumer = false;

{
// 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.
var 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: function () {
if (!hasWarnedAboutUsingConsumerProvider) {
hasWarnedAboutUsingConsumerProvider = true;
};
} else {
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};

error(
"Rendering <Context.Consumer.Provider> is not supported and will be removed in " +
"a future major release. Did you mean to render <Context.Provider> instead?"
);
{
var Consumer = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context
};
Object.defineProperties(Consumer, {
Provider: {
get: function () {
return context.Provider;
},
set: function (_Provider) {
context.Provider = _Provider;
}

return context.Provider;
},
set: function (_Provider) {
context.Provider = _Provider;
}
},
_currentValue: {
get: function () {
return context._currentValue;
},
set: function (_currentValue) {
context._currentValue = _currentValue;
}
},
_currentValue2: {
get: function () {
return context._currentValue2;
_currentValue: {
get: function () {
return context._currentValue;
},
set: function (_currentValue) {
context._currentValue = _currentValue;
}
},
set: function (_currentValue2) {
context._currentValue2 = _currentValue2;
}
},
_threadCount: {
get: function () {
return context._threadCount;
_currentValue2: {
get: function () {
return context._currentValue2;
},
set: function (_currentValue2) {
context._currentValue2 = _currentValue2;
}
},
set: function (_threadCount) {
context._threadCount = _threadCount;
}
},
Consumer: {
get: function () {
if (!hasWarnedAboutUsingNestedContextConsumers) {
hasWarnedAboutUsingNestedContextConsumers = true;

error(
"Rendering <Context.Consumer.Consumer> is not supported and will be removed in " +
"a future major release. Did you mean to render <Context.Consumer> instead?"
);
_threadCount: {
get: function () {
return context._threadCount;
},
set: function (_threadCount) {
context._threadCount = _threadCount;
}

return context.Consumer;
}
},
displayName: {
get: function () {
return context.displayName;
},
set: function (displayName) {
if (!hasWarnedAboutDisplayNameOnConsumer) {
warn(
"Setting `displayName` on Context.Consumer has no effect. " +
"You should set it directly on the context with Context.displayName = '%s'.",
displayName
);

hasWarnedAboutDisplayNameOnConsumer = true;
Consumer: {
get: function () {
return context.Consumer;
}
},
displayName: {
get: function () {
return context.displayName;
},
set: function (displayName) {}
}
}
}); // $FlowFixMe[prop-missing]: Flow complains about missing properties because it doesn't understand defineProperty

context.Consumer = Consumer;
});
context.Consumer = Consumer;
}
}

{
Expand Down Expand Up @@ -2910,22 +2903,11 @@ if (__DEV__) {
var dispatcher = resolveDispatcher();

{
// TODO: add a more generic warning for invalid values.
if (Context._context !== undefined) {
var realContext = Context._context; // Don't deduplicate because this legitimately causes bugs
// and nobody should be using this in existing code.

if (realContext.Consumer === Context) {
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) {
error(
"Calling useContext(Context.Provider) is not supported. " +
"Did you mean to call useContext(Context) instead?"
);
}
if (Context.$$typeof === REACT_CONSUMER_TYPE) {
error(
"Calling useContext(Context.Consumer) is not supported and will cause bugs. " +
"Did you mean to call useContext(Context) instead?"
);
}
}

Expand Down
Loading

0 comments on commit 1985f4f

Please sign in to comment.