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