@samitouri / QOS-React / commits / 3b7ee26925

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;