Skip to content

Commit 3b7ee26

Browse files
authored
Deprecate context object as a consumer and add a warning message (#13829)
* Deprecate context object as a consumer and add various warning messages for unsupported usage.
1 parent 8ca8a59 commit 3b7ee26

File tree

3 files changed

+207
-4
lines changed

3 files changed

+207
-4
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -1101,12 +1101,35 @@ function updateContextProvider(
11011101
return workInProgress.child;
11021102
}
11031103

1104+
let hasWarnedAboutUsingContextAsConsumer = false;
1105+
11041106
function updateContextConsumer(
11051107
current: Fiber | null,
11061108
workInProgress: Fiber,
11071109
renderExpirationTime: ExpirationTime,
11081110
) {
1109-
const context: ReactContext<any> = workInProgress.type;
1111+
let context: ReactContext<any> = workInProgress.type;
1112+
// The logic below for Context differs depending on PROD or DEV mode. In
1113+
// DEV mode, we create a separate object for Context.Consumer that acts
1114+
// like a proxy to Context. This proxy object adds unnecessary code in PROD
1115+
// so we use the old behaviour (Context.Consumer references Context) to
1116+
// reduce size and overhead. The separate object references context via
1117+
// a property called "_context", which also gives us the ability to check
1118+
// in DEV mode if this property exists or not and warn if it does not.
1119+
if (__DEV__) {
1120+
if ((context: any)._context === undefined) {
1121+
if (!hasWarnedAboutUsingContextAsConsumer) {
1122+
hasWarnedAboutUsingContextAsConsumer = true;
1123+
warning(
1124+
false,
1125+
'Rendering <Context> directly is not supported and will be removed in ' +
1126+
'a future major release. Did you mean to render <Context.Consumer> instead?',
1127+
);
1128+
}
1129+
} else {
1130+
context = (context: any)._context;
1131+
}
1132+
}
11101133
const newProps = workInProgress.pendingProps;
11111134
const render = newProps.children;
11121135

packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js

+103-2
Original file line numberDiff line numberDiff line change
@@ -1190,12 +1190,12 @@ describe('ReactNewContext', () => {
11901190

11911191
function FooAndBar() {
11921192
return (
1193-
<FooContext>
1193+
<FooContext.Consumer>
11941194
{foo => {
11951195
const bar = BarContext.unstable_read();
11961196
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
11971197
}}
1198-
</FooContext>
1198+
</FooContext.Consumer>
11991199
);
12001200
}
12011201

@@ -1558,4 +1558,105 @@ Context fuzz tester error! Copy and paste the following line into the test suite
15581558
}
15591559
});
15601560
});
1561+
1562+
it('should warn with an error message when using context as a consumer in DEV', () => {
1563+
const BarContext = React.createContext({value: 'bar-initial'});
1564+
const BarConsumer = BarContext;
1565+
1566+
function Component() {
1567+
return (
1568+
<React.Fragment>
1569+
<BarContext.Provider value={{value: 'bar-updated'}}>
1570+
<BarConsumer>
1571+
{({value}) => <div actual={value} expected="bar-updated" />}
1572+
</BarConsumer>
1573+
</BarContext.Provider>
1574+
</React.Fragment>
1575+
);
1576+
}
1577+
1578+
expect(() => {
1579+
ReactNoop.render(<Component />);
1580+
ReactNoop.flush();
1581+
}).toWarnDev(
1582+
'Rendering <Context> directly is not supported and will be removed in ' +
1583+
'a future major release. Did you mean to render <Context.Consumer> instead?',
1584+
);
1585+
});
1586+
1587+
it('should warn with an error message when using nested context consumers in DEV', () => {
1588+
const BarContext = React.createContext({value: 'bar-initial'});
1589+
const BarConsumer = BarContext;
1590+
1591+
function Component() {
1592+
return (
1593+
<React.Fragment>
1594+
<BarContext.Provider value={{value: 'bar-updated'}}>
1595+
<BarConsumer.Consumer.Consumer>
1596+
{({value}) => <div actual={value} expected="bar-updated" />}
1597+
</BarConsumer.Consumer.Consumer>
1598+
</BarContext.Provider>
1599+
</React.Fragment>
1600+
);
1601+
}
1602+
1603+
expect(() => {
1604+
ReactNoop.render(<Component />);
1605+
ReactNoop.flush();
1606+
}).toWarnDev(
1607+
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
1608+
'a future major release. Did you mean to render <Context.Consumer> instead?',
1609+
);
1610+
});
1611+
1612+
it('should warn with an error message when using Context.Consumer.unstable_read() DEV', () => {
1613+
const BarContext = React.createContext({value: 'bar-initial'});
1614+
1615+
function Child() {
1616+
let value = BarContext.Consumer.unstable_read();
1617+
return <div actual={value} expected="bar-updated" />;
1618+
}
1619+
1620+
function Component() {
1621+
return (
1622+
<React.Fragment>
1623+
<BarContext.Provider value={{value: 'bar-updated'}}>
1624+
<Child />
1625+
</BarContext.Provider>
1626+
</React.Fragment>
1627+
);
1628+
}
1629+
1630+
expect(() => {
1631+
ReactNoop.render(<Component />);
1632+
ReactNoop.flush();
1633+
}).toWarnDev(
1634+
'Calling Context.Consumer.unstable_read() is not supported and will be removed in ' +
1635+
'a future major release. Did you mean to render Context.unstable_read() instead?',
1636+
);
1637+
});
1638+
1639+
it('should warn with an error message when using Context.Consumer.Provider DEV', () => {
1640+
const BarContext = React.createContext({value: 'bar-initial'});
1641+
1642+
function Component() {
1643+
return (
1644+
<React.Fragment>
1645+
<BarContext.Consumer.Provider value={{value: 'bar-updated'}}>
1646+
<BarContext.Consumer>
1647+
{({value}) => <div actual={value} expected="bar-updated" />}
1648+
</BarContext.Consumer>
1649+
</BarContext.Consumer.Provider>
1650+
</React.Fragment>
1651+
);
1652+
}
1653+
1654+
expect(() => {
1655+
ReactNoop.render(<Component />);
1656+
ReactNoop.flush();
1657+
}).toWarnDev(
1658+
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
1659+
'a future major release. Did you mean to render <Context.Provider> instead?',
1660+
);
1661+
});
15611662
});

packages/react/src/ReactContext.js

+80-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {ReactContext} from 'shared/ReactTypes';
1313

1414
import invariant from 'shared/invariant';
1515
import warningWithoutStack from 'shared/warningWithoutStack';
16+
import warning from 'shared/warning';
1617

1718
import ReactCurrentOwner from './ReactCurrentOwner';
1819

@@ -67,9 +68,87 @@ export function createContext<T>(
6768
$$typeof: REACT_PROVIDER_TYPE,
6869
_context: context,
6970
};
70-
context.Consumer = context;
71+
7172
context.unstable_read = readContext.bind(null, context);
7273

74+
let hasWarnedAboutUsingNestedContextConsumers = false;
75+
let hasWarnedAboutUsingConsumerUnstableRead = false;
76+
let hasWarnedAboutUsingConsumerProvider = false;
77+
78+
if (__DEV__) {
79+
// A separate object, but proxies back to the original context object for
80+
// backwards compatibility. It has a different $$typeof, so we can properly
81+
// warn for the incorrect usage of Context as a Consumer.
82+
const Consumer = {
83+
$$typeof: REACT_CONTEXT_TYPE,
84+
_context: context,
85+
_calculateChangedBits: context._calculateChangedBits,
86+
unstable_read() {
87+
if (!hasWarnedAboutUsingConsumerUnstableRead) {
88+
hasWarnedAboutUsingConsumerUnstableRead = true;
89+
warning(
90+
false,
91+
'Calling Context.Consumer.unstable_read() is not supported and will be removed in ' +
92+
'a future major release. Did you mean to render Context.unstable_read() instead?',
93+
);
94+
}
95+
return context.unstable_read();
96+
},
97+
};
98+
// $FlowFixMe: Flow complains about not setting a value, which is intentional here
99+
Object.defineProperties(Consumer, {
100+
Provider: {
101+
get() {
102+
if (!hasWarnedAboutUsingConsumerProvider) {
103+
hasWarnedAboutUsingConsumerProvider = true;
104+
warning(
105+
false,
106+
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
107+
'a future major release. Did you mean to render <Context.Provider> instead?',
108+
);
109+
}
110+
return context.Provider;
111+
},
112+
set(_Provider) {
113+
context.Provider = _Provider;
114+
},
115+
},
116+
_currentValue: {
117+
get() {
118+
return context._currentValue;
119+
},
120+
set(_currentValue) {
121+
context._currentValue = _currentValue;
122+
},
123+
},
124+
_currentValue2: {
125+
get() {
126+
return context._currentValue2;
127+
},
128+
set(_currentValue2) {
129+
context._currentValue2 = _currentValue2;
130+
},
131+
},
132+
Consumer: {
133+
get() {
134+
if (!hasWarnedAboutUsingNestedContextConsumers) {
135+
hasWarnedAboutUsingNestedContextConsumers = true;
136+
warning(
137+
false,
138+
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
139+
'a future major release. Did you mean to render <Context.Consumer> instead?',
140+
);
141+
}
142+
return context.Consumer;
143+
},
144+
},
145+
});
146+
// $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
147+
context.Consumer = Consumer;
148+
} else {
149+
context.Consumer = context;
150+
}
151+
73152
if (__DEV__) {
74153
context._currentRenderer = null;
75154
context._currentRenderer2 = null;

0 commit comments

Comments
 (0)