Feature flag to disable legacy context for function components (#30319)
While the goal is to remove legacy context completely, I think we can already land the removal of legacy context for function components. I didn't even know this feature existed until reading the code recently. The win is just a couple of property lookups on function renders, but it trims down the API already as the full removal will likely still take a bit more time. www: Starting with enabled test renderer and a feature flag for production rollout. RN: Not enabled, will follow up on this.
Jan Kassens committed
Jul 11, 2024 at 16:21 UTC
af28f480e8e74ce71bb33259b61fef8a5a228f74
15 files changed
+48
-9
packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js
+24
@@ -86,6 +86,9 @@ describe('ReactDOMServerIntegration', () => {
86
});
87
88
itRenders('stateless child with context', async render => {
89
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
90
+ return;
91
+ }
92
function FunctionChildWithContext(props, context) {
93
return <div>{context.text}</div>;
94
}
@@ -118,6 +121,9 @@ describe('ReactDOMServerIntegration', () => {
121
});
122
123
itRenders('stateless child without context', async render => {
124
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
125
+ return;
126
+ }
127
function FunctionChildWithoutContext(props, context) {
128
// this should render blank; context isn't passed to this component.
129
return <div>{context.text}</div>;
@@ -151,6 +157,9 @@ describe('ReactDOMServerIntegration', () => {
157
});
158
159
itRenders('stateless child with wrong context', async render => {
160
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
161
+ return;
162
+ }
163
function FunctionChildWithWrongContext(props, context) {
164
// this should render blank; context.text isn't passed to this component.
165
return <div id="statelessWrongChild">{context.text}</div>;
@@ -169,6 +178,9 @@ describe('ReactDOMServerIntegration', () => {
178
});
179
180
itRenders('with context passed through to a grandchild', async render => {
181
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
182
+ return;
183
+ }
184
function Grandchild(props, context) {
185
return <div>{context.text}</div>;
186
}
@@ -186,6 +198,9 @@ describe('ReactDOMServerIntegration', () => {
198
});
199
200
itRenders('a child context overriding a parent context', async render => {
201
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
202
+ return;
203
+ }
204
const Grandchild = (props, context) => {
205
return <div>{context.text}</div>;
206
};
@@ -203,6 +218,9 @@ describe('ReactDOMServerIntegration', () => {
218
});
219
220
itRenders('a child context merged with a parent context', async render => {
221
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
222
+ return;
223
+ }
224
class Parent extends React.Component {
225
getChildContext() {
226
return {text1: 'purple'};
@@ -244,6 +262,9 @@ describe('ReactDOMServerIntegration', () => {
262
itRenders(
263
'with a call to componentWillMount before getChildContext',
264
async render => {
265
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
266
+ return;
267
+ }
268
class WillMountContext extends React.Component {
269
getChildContext() {
270
return {text: this.state.text};
@@ -270,6 +291,9 @@ describe('ReactDOMServerIntegration', () => {
291
itRenders(
292
'if getChildContext exists but childContextTypes is missing with a warning',
293
async render => {
294
+ if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
295
+ return;
296
+ }
297
function HopefulChild(props, context) {
298
return context.foo || 'nope';
299
}
packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
+1
-1
@@ -451,7 +451,7 @@ describe('ReactFunctionComponent', () => {
451
]);
452
});
453
454
- // @gate !disableLegacyContext
454
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
455
it('should receive context', async () => {
456
class Parent extends React.Component {
457
static childContextTypes = {
packages/react-reconciler/src/ReactFiberBeginWork.js
+2
-1
@@ -95,6 +95,7 @@ import {
95
import {
96
debugRenderPhaseSideEffectsForStrictMode,
97
disableLegacyContext,
98
+ disableLegacyContextForFunctionComponents,
99
enableProfilerCommitHooks,
100
enableProfilerTimer,
101
enableScopeAPI,
@@ -1158,7 +1159,7 @@ function updateFunctionComponent(
1159
}
1160
1161
let context;
1161
- if (!disableLegacyContext) {
1162
+ if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
1163
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
1164
context = getMaskedContext(workInProgress, unmaskedContext);
1165
}
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+3
-3
@@ -1701,7 +1701,7 @@ describe('ReactIncremental', () => {
1701
expect(instance.state.n).toEqual(3);
1702
});
1703
1704
- // @gate !disableLegacyContext
1704
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
1705
it('merges and masks context', async () => {
1706
class Intl extends React.Component {
1707
static childContextTypes = {
@@ -1954,7 +1954,7 @@ describe('ReactIncremental', () => {
1954
]);
1955
});
1956
1957
- // @gate !disableLegacyContext
1957
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
1958
it('reads context when setState is below the provider', async () => {
1959
let statefulInst;
1960
@@ -2046,7 +2046,7 @@ describe('ReactIncremental', () => {
2046
assertLog([]);
2047
});
2048
2049
- // @gate !disableLegacyContext
2049
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
2050
it('reads context when setState is above the provider', async () => {
2051
let statefulInst;
2052
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+1
-1
@@ -1158,7 +1158,7 @@ describe('ReactIncrementalErrorHandling', () => {
1158
// because it's used for new context, suspense, and many other features.
1159
// It has to be tested independently for each feature anyway. So although it
1160
// doesn't look like it, this test is specific to legacy context.
1161
- // @gate !disableLegacyContext
1161
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
1162
it('unwinds the context stack correctly on error', async () => {
1163
class Provider extends React.Component {
1164
static childContextTypes = {message: PropTypes.string};
packages/react-reconciler/src/__tests__/ReactUse-test.js
+1
-1
@@ -1562,7 +1562,7 @@ describe('ReactUse', () => {
1562
expect(root).toMatchRenderedOutput('Async!');
1563
});
1564
1565
- // @gate !disableLegacyContext
1565
+ // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
1566
it('unwrap uncached promises in component that accesses legacy context', async () => {
1567
class ContextProvider extends React.Component {
1568
static childContextTypes = {
packages/react-server/src/ReactFizzServer.js
+2
-1
@@ -152,6 +152,7 @@ import {
152
import ReactSharedInternals from 'shared/ReactSharedInternals';
153
import {
154
disableLegacyContext,
155
+ disableLegacyContextForFunctionComponents,
156
enableScopeAPI,
157
enableSuspenseAvoidThisFallbackFizz,
158
enableCache,
@@ -1654,7 +1655,7 @@ function renderFunctionComponent(
1655
props: any,
1656
): void {
1657
let legacyContext;
1657
- if (!disableLegacyContext) {
1658
+ if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
1659
legacyContext = getMaskedContext(Component, task.legacyContext);
1660
}
1661
if (__DEV__) {
packages/shared/ReactFeatureFlags.js
+7
-1
@@ -155,8 +155,14 @@ export const transitionLaneExpirationMs = 5000;
155
// Renames the internal symbol for elements since they have changed signature/constructor
156
export const renameElementSymbol = true;
157
158
-// Removes legacy style context
158
+/**
159
+ * Removes legacy style context defined using static `contextTypes` and consumed with static `childContextTypes`.
160
+ */
161
export const disableLegacyContext = true;
162
+/**
163
+ * Removes legacy style context just from function components.
164
+ */
165
+export const disableLegacyContextForFunctionComponents = true;
166
167
// Not ready to break experimental yet.
168
// Modern <StrictMode /> behaviour aligns more with what components
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -37,6 +37,7 @@ export const disableCommentsAsDOMContainers = true;
37
export const disableIEWorkarounds = true;
38
export const disableInputAttributeSyncing = false;
39
export const disableLegacyContext = false;
40
+export const disableLegacyContextForFunctionComponents = false;
41
export const disableLegacyMode = false;
42
export const disableSchedulerTimeoutInWorkLoop = false;
43
export const disableStringRefs = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -28,6 +28,7 @@ export const disableDefaultPropsExceptForClasses = true;
28
export const disableIEWorkarounds = true;
29
export const disableInputAttributeSyncing = false;
30
export const disableLegacyContext = true;
31
+export const disableLegacyContextForFunctionComponents = true;
32
export const disableLegacyMode = false;
33
export const disableSchedulerTimeoutInWorkLoop = false;
34
export const disableStringRefs = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -92,6 +92,7 @@ export const disableStringRefs = true;
92
export const enableFastJSX = true;
93
export const disableLegacyMode = true;
94
export const disableLegacyContext = true;
95
+export const disableLegacyContextForFunctionComponents = true;
96
export const enableRenderableContext = true;
97
export const enableReactTestRendererWarning = true;
98
export const disableDefaultPropsExceptForClasses = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -20,6 +20,7 @@ export const disableDefaultPropsExceptForClasses = false;
20
export const disableIEWorkarounds = true;
21
export const disableInputAttributeSyncing = false;
22
export const disableLegacyContext = false;
23
+export const disableLegacyContextForFunctionComponents = false;
24
export const disableLegacyMode = false;
25
export const disableSchedulerTimeoutInWorkLoop = false;
26
export const disableStringRefs = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -32,6 +32,7 @@ export const enableScopeAPI = true;
32
export const enableCreateEventHandleAPI = false;
33
export const enableSuspenseCallback = true;
34
export const disableLegacyContext = false;
35
+export const disableLegacyContextForFunctionComponents = false;
36
export const enableTrustedTypesIntegration = false;
37
export const disableTextareaChildren = false;
38
export const enableSuspenseAvoidThisFallback = true;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -15,6 +15,7 @@
15
16
export const alwaysThrottleRetries = true;
17
export const disableDefaultPropsExceptForClasses = __VARIANT__;
18
+export const disableLegacyContextForFunctionComponents = __VARIANT__;
19
export const disableLegacyMode = __VARIANT__;
20
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
21
export const enableAddPropertiesFastPath = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -17,6 +17,7 @@ const dynamicFeatureFlags: DynamicFeatureFlags = require('ReactFeatureFlags');
17
export const {
18
alwaysThrottleRetries,
19
disableDefaultPropsExceptForClasses,
20
+ disableLegacyContextForFunctionComponents,
21
disableSchedulerTimeoutInWorkLoop,
22
enableAddPropertiesFastPath,
23
enableDebugTracing,