@samitouri / QOS-React-2 / commits / 0beb2ee76b

Fix incorrect legacy context for factory components (#13441)

* Add a repro case for context bug * Be explicit about whether context has been pushed * Put cheaper check first * Naming

Dan Abramov committed Aug 20, 2018 at 16:08 UTC 0beb2ee76b96ffb1c15a1770a6907ea1cd5a5696
4 files changed +50 -9
packages/react-reconciler/src/ReactFiberBeginWork.js
+2 -2
@@ -262,7 +262,7 @@ function updateFunctionalComponent(
262 nextProps: any,
263 renderExpirationTime,
264 ) {
265 - const unmaskedContext = getUnmaskedContext(workInProgress, Component);
265 + const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
266 const context = getMaskedContext(workInProgress, unmaskedContext);
267
268 let nextChildren;
@@ -674,7 +674,7 @@ function mountIndeterminateComponent(
674 }
675 }
676
677 - const unmaskedContext = getUnmaskedContext(workInProgress, Component);
677 + const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
678 const context = getMaskedContext(workInProgress, unmaskedContext);
679
680 prepareToReadContext(workInProgress, renderExpirationTime);
packages/react-reconciler/src/ReactFiberClassComponent.js
+12 -4
@@ -473,7 +473,7 @@ function constructClassInstance(
473 props: any,
474 renderExpirationTime: ExpirationTime,
475 ): any {
476 - const unmaskedContext = getUnmaskedContext(workInProgress, ctor);
476 + const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
477 const contextTypes = ctor.contextTypes;
478 const isContextConsumer = contextTypes !== null && contextTypes !== undefined;
479 const context = isContextConsumer
@@ -666,7 +666,7 @@ function mountClassInstance(
666 }
667
668 const instance = workInProgress.stateNode;
669 - const unmaskedContext = getUnmaskedContext(workInProgress, ctor);
669 + const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
670
671 instance.props = newProps;
672 instance.state = workInProgress.memoizedState;
@@ -758,7 +758,11 @@ function resumeMountClassInstance(
758 instance.props = oldProps;
759
760 const oldContext = instance.context;
761 - const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress, ctor);
761 + const nextLegacyUnmaskedContext = getUnmaskedContext(
762 + workInProgress,
763 + ctor,
764 + true,
765 + );
766 const nextLegacyContext = getMaskedContext(
767 workInProgress,
768 nextLegacyUnmaskedContext,
@@ -897,7 +901,11 @@ function updateClassInstance(
901 instance.props = oldProps;
902
903 const oldContext = instance.context;
900 - const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress, ctor);
904 + const nextLegacyUnmaskedContext = getUnmaskedContext(
905 + workInProgress,
906 + ctor,
907 + true,
908 + );
909 const nextLegacyContext = getMaskedContext(
910 workInProgress,
911 nextLegacyUnmaskedContext,
packages/react-reconciler/src/ReactFiberContext.js
+3 -3
@@ -49,11 +49,11 @@ let previousContext: Object = emptyContextObject;
49 function getUnmaskedContext(
50 workInProgress: Fiber,
51 Component: Function,
52 + didPushOwnContextIfProvider: boolean,
53 ): Object {
53 - const hasOwnContext = isContextProvider(Component);
54 - if (hasOwnContext) {
54 + if (didPushOwnContextIfProvider && isContextProvider(Component)) {
55 // If the fiber is a context provider itself, when we read its context
56 - // we have already pushed its own child context on the stack. A context
56 + // we may have already pushed its own child context on the stack. A context
57 // provider should not "see" its own child context. Therefore we read the
58 // previous (parent) context instead for a context provider.
59 return previousContext;
packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js
+33
@@ -2030,6 +2030,39 @@ describe('ReactIncremental', () => {
2030 ]);
2031 });
2032
2033 + it('does not leak own context into context provider (factory components)', () => {
2034 + const ops = [];
2035 + function Recurse(props, context) {
2036 + return {
2037 + getChildContext() {
2038 + return {n: (context.n || 3) - 1};
2039 + },
2040 + render() {
2041 + ops.push('Recurse ' + JSON.stringify(context));
2042 + if (context.n === 0) {
2043 + return null;
2044 + }
2045 + return <Recurse />;
2046 + },
2047 + };
2048 + }
2049 + Recurse.contextTypes = {
2050 + n: PropTypes.number,
2051 + };
2052 + Recurse.childContextTypes = {
2053 + n: PropTypes.number,
2054 + };
2055 +
2056 + ReactNoop.render(<Recurse />);
2057 + ReactNoop.flush();
2058 + expect(ops).toEqual([
2059 + 'Recurse {}',
2060 + 'Recurse {"n":2}',
2061 + 'Recurse {"n":1}',
2062 + 'Recurse {"n":0}',
2063 + ]);
2064 + });
2065 +
2066 it('provides context when reusing work', () => {
2067 const ops = [];
2068