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;
}