diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 3c20dceacf3fb..527e6787d1f45 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -859,8 +859,10 @@ export default function( const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps; + let canBailOnProps = true; if (hasLegacyContextChanged()) { + canBailOnProps = false; // Normally we can bail out on props equality but if context has changed // we don't do the bailout and we have to reuse existing props instead. } else if (oldProps === newProps) { @@ -893,7 +895,7 @@ export default function( } else { if (oldProps.value === newProps.value) { // No change. Bailout early if children are the same. - if (oldProps.children === newProps.children) { + if (oldProps.children === newProps.children && canBailOnProps) { workInProgress.stateNode = 0; pushProvider(workInProgress); return bailoutOnAlreadyFinishedWork(current, workInProgress); @@ -910,7 +912,7 @@ export default function( (oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare ) { // No change. Bailout early if children are the same. - if (oldProps.children === newProps.children) { + if (oldProps.children === newProps.children && canBailOnProps) { workInProgress.stateNode = 0; pushProvider(workInProgress); return bailoutOnAlreadyFinishedWork(current, workInProgress); @@ -933,7 +935,7 @@ export default function( if (changedBits === 0) { // No change. Bailout early if children are the same. - if (oldProps.children === newProps.children) { + if (oldProps.children === newProps.children && canBailOnProps) { workInProgress.stateNode = 0; pushProvider(workInProgress); return bailoutOnAlreadyFinishedWork(current, workInProgress); diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js index 0a4763a4fec8a..db2703d3a70b5 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js @@ -847,6 +847,72 @@ describe('ReactNewContext', () => { expect(ReactNoop.getChildren()).toEqual([span('Child')]); }); + it('provider does not bail out if legacy context changed above', () => { + const Context = React.createContext(0); + + function Child() { + ReactNoop.yield('Child'); + return ; + } + + const children = ; + + class LegacyProvider extends React.Component { + static childContextTypes = { + legacyValue: () => {}, + }; + state = {legacyValue: 1}; + getChildContext() { + return {legacyValue: this.state.legacyValue}; + } + render() { + ReactNoop.yield('LegacyProvider'); + return this.props.children; + } + } + + class App extends React.Component { + state = {value: 1}; + render() { + ReactNoop.yield('App'); + return ( + + {this.props.children} + + ); + } + } + + const legacyProviderRef = React.createRef(); + const appRef = React.createRef(); + + // Initial mount + ReactNoop.render( + + + {children} + + , + ); + expect(ReactNoop.flush()).toEqual(['LegacyProvider', 'App', 'Child']); + expect(ReactNoop.getChildren()).toEqual([span('Child')]); + + // Update App with same value (should bail out) + appRef.current.setState({value: 1}); + expect(ReactNoop.flush()).toEqual(['App']); + expect(ReactNoop.getChildren()).toEqual([span('Child')]); + + // Update LegacyProvider (should not bail out) + legacyProviderRef.current.setState({value: 1}); + expect(ReactNoop.flush()).toEqual(['LegacyProvider', 'App', 'Child']); + expect(ReactNoop.getChildren()).toEqual([span('Child')]); + + // Update App with same value (should bail out) + appRef.current.setState({value: 1}); + expect(ReactNoop.flush()).toEqual(['App']); + expect(ReactNoop.getChildren()).toEqual([span('Child')]); + }); + it('consumer bails out if value is unchanged and something above bailed out', () => { const Context = React.createContext(0);