diff --git a/src/renderers/__tests__/ReactUpdates-test.js b/src/renderers/__tests__/ReactUpdates-test.js index fdfb92c532..f3cc2dd5ca 100644 --- a/src/renderers/__tests__/ReactUpdates-test.js +++ b/src/renderers/__tests__/ReactUpdates-test.js @@ -1072,6 +1072,108 @@ describe('ReactUpdates', () => { expect(ops).toEqual(['Hello', '']); }); + it( + 'in sync mode, updates in componentWillUpdate and componentDidUpdate ' + + 'should both flush in the immediately subsequent commit', + () => { + let ops = []; + class Foo extends React.Component { + state = {a: false, b: false}; + componentWillUpdate(_, nextState) { + if (!nextState.a) { + this.setState({a: true}); + } + } + componentDidUpdate() { + ops.push('Foo updated'); + if (!this.state.b) { + this.setState({b: true}); + } + } + render() { + ops.push(`a: ${this.state.a}, b: ${this.state.b}`); + return null; + } + } + + const container = document.createElement('div'); + // Mount + ReactDOM.render(, container); + // Root update + ReactDOM.render(, container); + expect(ops).toEqual([ + // Mount + 'a: false, b: false', + // Root update + 'a: false, b: false', + 'Foo updated', + // Subsequent update (both a and b should have flushed) + 'a: true, b: true', + 'Foo updated', + // There should not be any additional updates + ]); + }, + ); + + it( + 'in sync mode, updates in componentWillUpdate and componentDidUpdate ' + + '(on a sibling) should both flush in the immediately subsequent commit', + () => { + let ops = []; + class Foo extends React.Component { + state = {a: false}; + componentWillUpdate(_, nextState) { + if (!nextState.a) { + this.setState({a: true}); + } + } + componentDidUpdate() { + ops.push('Foo updated'); + } + render() { + ops.push(`a: ${this.state.a}`); + return null; + } + } + + class Bar extends React.Component { + state = {b: false}; + componentDidUpdate() { + ops.push('Bar updated'); + if (!this.state.b) { + this.setState({b: true}); + } + } + render() { + ops.push(`b: ${this.state.b}`); + return null; + } + } + + const container = document.createElement('div'); + // Mount + ReactDOM.render(
, container); + // Root update + ReactDOM.render(
, container); + expect(ops).toEqual([ + // Mount + 'a: false', + 'b: false', + // Root update + 'a: false', + 'b: false', + 'Foo updated', + 'Bar updated', + // Subsequent update (both a and b should have flushed) + 'a: true', + 'b: true', + 'Foo updated', + 'Bar updated', + // There should not be any additional updates + ]); + }, + ); + it('does not re-render if state update is null', () => { let container = document.createElement('div'); diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 479c730b49..bf026e60f4 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -1494,7 +1494,7 @@ module.exports = function( if (isCommitting) { // Updates that occur during the commit phase should have task priority // by default. - expirationTime = Task; + expirationTime = Sync; } else { // Updates during the render phase should expire at the same time as // the work that is being rendered. @@ -1517,7 +1517,10 @@ module.exports = function( } } - if (expirationTime === Sync && (isCommitting || isBatchingUpdates)) { + if ( + expirationTime === Sync && + (isBatchingUpdates || (isUnbatchingUpdates && isCommitting)) + ) { // If we're in a batch, or in the commit phase, downgrade sync to task return Task; }