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.
Dominic Gannaway committed
Oct 12, 2018 at 17:46 UTC
3b7ee269259c077956af63b2ad34cbd5b6b15d27
3 files changed
+207
-4
packages/react-reconciler/src/ReactFiberBeginWork.js
+24
-1
@@ -1101,12 +1101,35 @@ function updateContextProvider(
1101
return workInProgress.child;
1102
}
1103
1104
+let hasWarnedAboutUsingContextAsConsumer = false;
1105
+
1106
function updateContextConsumer(
1107
current: Fiber | null,
1108
workInProgress: Fiber,
1109
renderExpirationTime: ExpirationTime,
1110
) {
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
+ }
1133
const newProps = workInProgress.pendingProps;
1134
const render = newProps.children;
1135
packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js
+103
-2
@@ -1190,12 +1190,12 @@ describe('ReactNewContext', () => {
1190
1191
function FooAndBar() {
1192
return (
1193
- <FooContext>
1193
+ <FooContext.Consumer>
1194
{foo => {
1195
const bar = BarContext.unstable_read();
1196
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
1197
}}
1198
- </FooContext>
1198
+ </FooContext.Consumer>
1199
);
1200
}
1201
@@ -1558,4 +1558,105 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1558
}
1559
});
1560
});
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
+ });
1662
});
packages/react/src/ReactContext.js
+80
-1
@@ -13,6 +13,7 @@ import type {ReactContext} from 'shared/ReactTypes';
13
14
import invariant from 'shared/invariant';
15
import warningWithoutStack from 'shared/warningWithoutStack';
16
+import warning from 'shared/warning';
17
18
import ReactCurrentOwner from './ReactCurrentOwner';
19
@@ -67,9 +68,87 @@ export function createContext<T>(
68
$$typeof: REACT_PROVIDER_TYPE,
69
_context: context,
70
};
70
- context.Consumer = context;
71
+
72
context.unstable_read = readContext.bind(null, context);
73
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
+
152
if (__DEV__) {
153
context._currentRenderer = null;
154
context._currentRenderer2 = null;