Skip to content

Commit 0c1ec04

Browse files
authored
Add a feature flag to disable legacy context (#16269)
* Add a feature flag to disable legacy context * Address review - invariant -> warning - Make this.context and context argument actually undefined * Increase test coverage for lifecycles * Also disable it on the server is flag is on * Make this.context {} when disabled, but function context is undefined * Move checks inside
1 parent 95674af commit 0c1ec04

14 files changed

+810
-278
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13+
14+
let React;
15+
let ReactDOM;
16+
let ReactFeatureFlags;
17+
let ReactDOMServer;
18+
let ReactTestUtils;
19+
20+
function initModules() {
21+
// Reset warning cache.
22+
jest.resetModuleRegistry();
23+
React = require('react');
24+
ReactDOM = require('react-dom');
25+
ReactDOMServer = require('react-dom/server');
26+
ReactTestUtils = require('react-dom/test-utils');
27+
28+
ReactFeatureFlags = require('shared/ReactFeatureFlags');
29+
ReactFeatureFlags.disableLegacyContext = true;
30+
31+
// Make them available to the helpers.
32+
return {
33+
ReactDOM,
34+
ReactDOMServer,
35+
ReactTestUtils,
36+
};
37+
}
38+
39+
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
40+
41+
function formatValue(val) {
42+
if (val === null) {
43+
return 'null';
44+
}
45+
if (val === undefined) {
46+
return 'undefined';
47+
}
48+
if (typeof val === 'string') {
49+
return val;
50+
}
51+
return JSON.stringify(val);
52+
}
53+
54+
describe('ReactDOMServerIntegrationLegacyContextDisabled', () => {
55+
beforeEach(() => {
56+
resetModules();
57+
});
58+
59+
itRenders('undefined legacy context with warning', async render => {
60+
class LegacyProvider extends React.Component {
61+
static childContextTypes = {
62+
foo() {},
63+
};
64+
getChildContext() {
65+
return {foo: 10};
66+
}
67+
render() {
68+
return this.props.children;
69+
}
70+
}
71+
72+
let lifecycleContextLog = [];
73+
class LegacyClsConsumer extends React.Component {
74+
static contextTypes = {
75+
foo() {},
76+
};
77+
shouldComponentUpdate(nextProps, nextState, nextContext) {
78+
lifecycleContextLog.push(nextContext);
79+
return true;
80+
}
81+
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
82+
lifecycleContextLog.push(nextContext);
83+
}
84+
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
85+
lifecycleContextLog.push(nextContext);
86+
}
87+
render() {
88+
return formatValue(this.context);
89+
}
90+
}
91+
92+
function LegacyFnConsumer(props, context) {
93+
return formatValue(context);
94+
}
95+
LegacyFnConsumer.contextTypes = {foo() {}};
96+
97+
function RegularFn(props, context) {
98+
return formatValue(context);
99+
}
100+
101+
const e = await render(
102+
<LegacyProvider>
103+
<span>
104+
<LegacyClsConsumer />
105+
<LegacyFnConsumer />
106+
<RegularFn />
107+
</span>
108+
</LegacyProvider>,
109+
3,
110+
);
111+
expect(e.textContent).toBe('{}undefinedundefined');
112+
expect(lifecycleContextLog).toEqual([]);
113+
});
114+
115+
itRenders('modern context', async render => {
116+
let Ctx = React.createContext();
117+
118+
class Provider extends React.Component {
119+
render() {
120+
return (
121+
<Ctx.Provider value={this.props.value}>
122+
{this.props.children}
123+
</Ctx.Provider>
124+
);
125+
}
126+
}
127+
128+
class RenderPropConsumer extends React.Component {
129+
render() {
130+
return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
131+
}
132+
}
133+
134+
let lifecycleContextLog = [];
135+
class ContextTypeConsumer extends React.Component {
136+
static contextType = Ctx;
137+
shouldComponentUpdate(nextProps, nextState, nextContext) {
138+
lifecycleContextLog.push(nextContext);
139+
return true;
140+
}
141+
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
142+
lifecycleContextLog.push(nextContext);
143+
}
144+
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
145+
lifecycleContextLog.push(nextContext);
146+
}
147+
render() {
148+
return formatValue(this.context);
149+
}
150+
}
151+
152+
function FnConsumer() {
153+
return formatValue(React.useContext(Ctx));
154+
}
155+
156+
const e = await render(
157+
<Provider value="a">
158+
<span>
159+
<RenderPropConsumer />
160+
<ContextTypeConsumer />
161+
<FnConsumer />
162+
</span>
163+
</Provider>,
164+
);
165+
expect(e.textContent).toBe('aaa');
166+
expect(lifecycleContextLog).toEqual([]);
167+
});
168+
});
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
let React;
13+
let ReactDOM;
14+
let ReactFeatureFlags;
15+
16+
describe('ReactLegacyContextDisabled', () => {
17+
beforeEach(() => {
18+
jest.resetModules();
19+
20+
React = require('react');
21+
ReactDOM = require('react-dom');
22+
ReactFeatureFlags = require('shared/ReactFeatureFlags');
23+
ReactFeatureFlags.disableLegacyContext = true;
24+
});
25+
26+
function formatValue(val) {
27+
if (val === null) {
28+
return 'null';
29+
}
30+
if (val === undefined) {
31+
return 'undefined';
32+
}
33+
if (typeof val === 'string') {
34+
return val;
35+
}
36+
return JSON.stringify(val);
37+
}
38+
39+
it('warns for legacy context', () => {
40+
class LegacyProvider extends React.Component {
41+
static childContextTypes = {
42+
foo() {},
43+
};
44+
getChildContext() {
45+
return {foo: 10};
46+
}
47+
render() {
48+
return this.props.children;
49+
}
50+
}
51+
52+
let lifecycleContextLog = [];
53+
class LegacyClsConsumer extends React.Component {
54+
static contextTypes = {
55+
foo() {},
56+
};
57+
shouldComponentUpdate(nextProps, nextState, nextContext) {
58+
lifecycleContextLog.push(nextContext);
59+
return true;
60+
}
61+
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
62+
lifecycleContextLog.push(nextContext);
63+
}
64+
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
65+
lifecycleContextLog.push(nextContext);
66+
}
67+
render() {
68+
return formatValue(this.context);
69+
}
70+
}
71+
72+
function LegacyFnConsumer(props, context) {
73+
return formatValue(context);
74+
}
75+
LegacyFnConsumer.contextTypes = {foo() {}};
76+
77+
function RegularFn(props, context) {
78+
return formatValue(context);
79+
}
80+
81+
const container = document.createElement('div');
82+
expect(() => {
83+
ReactDOM.render(
84+
<LegacyProvider>
85+
<span>
86+
<LegacyClsConsumer />
87+
<LegacyFnConsumer />
88+
<RegularFn />
89+
</span>
90+
</LegacyProvider>,
91+
container,
92+
);
93+
}).toWarnDev(
94+
[
95+
'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' +
96+
'Use React.createContext() instead.',
97+
'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' +
98+
'Use React.createContext() with static contextType instead.',
99+
'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
100+
'Use React.createContext() with React.useContext() instead.',
101+
],
102+
{withoutStack: true},
103+
);
104+
expect(container.textContent).toBe('{}undefinedundefined');
105+
expect(lifecycleContextLog).toEqual([]);
106+
107+
// Test update path.
108+
ReactDOM.render(
109+
<LegacyProvider>
110+
<span>
111+
<LegacyClsConsumer />
112+
<LegacyFnConsumer />
113+
<RegularFn />
114+
</span>
115+
</LegacyProvider>,
116+
container,
117+
);
118+
expect(container.textContent).toBe('{}undefinedundefined');
119+
expect(lifecycleContextLog).toEqual([{}, {}, {}]);
120+
ReactDOM.unmountComponentAtNode(container);
121+
});
122+
123+
it('renders a tree with modern context', () => {
124+
let Ctx = React.createContext();
125+
126+
class Provider extends React.Component {
127+
render() {
128+
return (
129+
<Ctx.Provider value={this.props.value}>
130+
{this.props.children}
131+
</Ctx.Provider>
132+
);
133+
}
134+
}
135+
136+
class RenderPropConsumer extends React.Component {
137+
render() {
138+
return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
139+
}
140+
}
141+
142+
let lifecycleContextLog = [];
143+
class ContextTypeConsumer extends React.Component {
144+
static contextType = Ctx;
145+
shouldComponentUpdate(nextProps, nextState, nextContext) {
146+
lifecycleContextLog.push(nextContext);
147+
return true;
148+
}
149+
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
150+
lifecycleContextLog.push(nextContext);
151+
}
152+
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
153+
lifecycleContextLog.push(nextContext);
154+
}
155+
render() {
156+
return formatValue(this.context);
157+
}
158+
}
159+
160+
function FnConsumer() {
161+
return formatValue(React.useContext(Ctx));
162+
}
163+
164+
const container = document.createElement('div');
165+
ReactDOM.render(
166+
<Provider value="a">
167+
<span>
168+
<RenderPropConsumer />
169+
<ContextTypeConsumer />
170+
<FnConsumer />
171+
</span>
172+
</Provider>,
173+
container,
174+
);
175+
expect(container.textContent).toBe('aaa');
176+
expect(lifecycleContextLog).toEqual([]);
177+
178+
// Test update path
179+
ReactDOM.render(
180+
<Provider value="a">
181+
<span>
182+
<RenderPropConsumer />
183+
<ContextTypeConsumer />
184+
<FnConsumer />
185+
</span>
186+
</Provider>,
187+
container,
188+
);
189+
expect(container.textContent).toBe('aaa');
190+
expect(lifecycleContextLog).toEqual(['a', 'a', 'a']);
191+
lifecycleContextLog.length = 0;
192+
193+
ReactDOM.render(
194+
<Provider value="b">
195+
<span>
196+
<RenderPropConsumer />
197+
<ContextTypeConsumer />
198+
<FnConsumer />
199+
</span>
200+
</Provider>,
201+
container,
202+
);
203+
expect(container.textContent).toBe('bbb');
204+
expect(lifecycleContextLog).toEqual(['b', 'b']); // sCU skipped due to changed context value.
205+
ReactDOM.unmountComponentAtNode(container);
206+
});
207+
});

0 commit comments

Comments
 (0)