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

Lines changed: 810 additions & 278 deletions
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+
constReactDOMServerIntegrationUtils=require('./utils/ReactDOMServerIntegrationTestUtils');
13+
14+
letReact;
15+
letReactDOM;
16+
letReactFeatureFlags;
17+
letReactDOMServer;
18+
letReactTestUtils;
19+
20+
functioninitModules(){
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+
functionformatValue(val){
42+
if(val===null){
43+
return'null';
44+
}
45+
if(val===undefined){
46+
return'undefined';
47+
}
48+
if(typeofval==='string'){
49+
returnval;
50+
}
51+
returnJSON.stringify(val);
52+
}
53+
54+
describe('ReactDOMServerIntegrationLegacyContextDisabled',()=>{
55+
beforeEach(()=>{
56+
resetModules();
57+
});
58+
59+
itRenders('undefined legacy context with warning',asyncrender=>{
60+
classLegacyProviderextendsReact.Component{
61+
staticchildContextTypes={
62+
foo(){},
63+
};
64+
getChildContext(){
65+
return{foo: 10};
66+
}
67+
render(){
68+
returnthis.props.children;
69+
}
70+
}
71+
72+
letlifecycleContextLog=[];
73+
classLegacyClsConsumerextendsReact.Component{
74+
staticcontextTypes={
75+
foo(){},
76+
};
77+
shouldComponentUpdate(nextProps,nextState,nextContext){
78+
lifecycleContextLog.push(nextContext);
79+
returntrue;
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+
returnformatValue(this.context);
89+
}
90+
}
91+
92+
functionLegacyFnConsumer(props,context){
93+
returnformatValue(context);
94+
}
95+
LegacyFnConsumer.contextTypes={foo(){}};
96+
97+
functionRegularFn(props,context){
98+
returnformatValue(context);
99+
}
100+
101+
conste=awaitrender(
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',asyncrender=>{
116+
letCtx=React.createContext();
117+
118+
classProviderextendsReact.Component{
119+
render(){
120+
return(
121+
<Ctx.Providervalue={this.props.value}>
122+
{this.props.children}
123+
</Ctx.Provider>
124+
);
125+
}
126+
}
127+
128+
classRenderPropConsumerextendsReact.Component{
129+
render(){
130+
return<Ctx.Consumer>{value=>formatValue(value)}</Ctx.Consumer>;
131+
}
132+
}
133+
134+
letlifecycleContextLog=[];
135+
classContextTypeConsumerextendsReact.Component{
136+
staticcontextType=Ctx;
137+
shouldComponentUpdate(nextProps,nextState,nextContext){
138+
lifecycleContextLog.push(nextContext);
139+
returntrue;
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+
returnformatValue(this.context);
149+
}
150+
}
151+
152+
functionFnConsumer(){
153+
returnformatValue(React.useContext(Ctx));
154+
}
155+
156+
conste=awaitrender(
157+
<Providervalue="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+
letReact;
13+
letReactDOM;
14+
letReactFeatureFlags;
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+
functionformatValue(val){
27+
if(val===null){
28+
return'null';
29+
}
30+
if(val===undefined){
31+
return'undefined';
32+
}
33+
if(typeofval==='string'){
34+
returnval;
35+
}
36+
returnJSON.stringify(val);
37+
}
38+
39+
it('warns for legacy context',()=>{
40+
classLegacyProviderextendsReact.Component{
41+
staticchildContextTypes={
42+
foo(){},
43+
};
44+
getChildContext(){
45+
return{foo: 10};
46+
}
47+
render(){
48+
returnthis.props.children;
49+
}
50+
}
51+
52+
letlifecycleContextLog=[];
53+
classLegacyClsConsumerextendsReact.Component{
54+
staticcontextTypes={
55+
foo(){},
56+
};
57+
shouldComponentUpdate(nextProps,nextState,nextContext){
58+
lifecycleContextLog.push(nextContext);
59+
returntrue;
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+
returnformatValue(this.context);
69+
}
70+
}
71+
72+
functionLegacyFnConsumer(props,context){
73+
returnformatValue(context);
74+
}
75+
LegacyFnConsumer.contextTypes={foo(){}};
76+
77+
functionRegularFn(props,context){
78+
returnformatValue(context);
79+
}
80+
81+
constcontainer=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+
letCtx=React.createContext();
125+
126+
classProviderextendsReact.Component{
127+
render(){
128+
return(
129+
<Ctx.Providervalue={this.props.value}>
130+
{this.props.children}
131+
</Ctx.Provider>
132+
);
133+
}
134+
}
135+
136+
classRenderPropConsumerextendsReact.Component{
137+
render(){
138+
return<Ctx.Consumer>{value=>formatValue(value)}</Ctx.Consumer>;
139+
}
140+
}
141+
142+
letlifecycleContextLog=[];
143+
classContextTypeConsumerextendsReact.Component{
144+
staticcontextType=Ctx;
145+
shouldComponentUpdate(nextProps,nextState,nextContext){
146+
lifecycleContextLog.push(nextContext);
147+
returntrue;
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+
returnformatValue(this.context);
157+
}
158+
}
159+
160+
functionFnConsumer(){
161+
returnformatValue(React.useContext(Ctx));
162+
}
163+
164+
constcontainer=document.createElement('div');
165+
ReactDOM.render(
166+
<Providervalue="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+
<Providervalue="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+
<Providervalue="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)