diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index 3b47c7c8ab..bb43da310f 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -99,6 +99,14 @@ var CompositeLifeCycle = keyMirror({ RECEIVING_PROPS: null }); +/** + * An incrementing ID assigned to each component when it is mounted. This is + * used to enforce the order in which `ReactUpdates` updates dirty components. + * + * @private + */ +var nextMountID = 1; + /** * @lends {ReactCompositeComponent.prototype} */ @@ -132,6 +140,7 @@ var ReactCompositeComponentMixin = assign({}, ReactComponent.Mixin.construct.apply(this, arguments); this._context = null; + this._mountOrder = 0; // See ReactUpdates. this._pendingCallbacks = null; @@ -167,6 +176,7 @@ var ReactCompositeComponentMixin = assign({}, ); this._context = context; + this._mountOrder = nextMountID++; this._rootNodeID = rootID; var inst = this._instance; diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index e8d59f2d6b..601d6a268c 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -110,14 +110,14 @@ function batchedUpdates(callback, a, b) { } /** - * Array comparator for ReactComponents by owner depth + * Array comparator for ReactComponents by mount ordering. * * @param {ReactComponent} c1 first component you're comparing * @param {ReactComponent} c2 second component you're comparing * @return {number} Return value usable by Array.prototype.sort(). */ -function mountDepthComparator(c1, c2) { - return c1._mountDepth - c2._mountDepth; +function mountOrderComparator(c1, c2) { + return c1._mountOrder - c2._mountOrder; } function runBatchedUpdates(transaction) { @@ -133,7 +133,7 @@ function runBatchedUpdates(transaction) { // Since reconciling a component higher in the owner hierarchy usually (not // always -- see shouldComponentUpdate()) will reconcile children, reconcile // them before their children by sorting the array. - dirtyComponents.sort(mountDepthComparator); + dirtyComponents.sort(mountOrderComparator); for (var i = 0; i < len; i++) { // If a component is unmounted before pending changes apply, it will still diff --git a/src/core/__tests__/ReactUpdates-test.js b/src/core/__tests__/ReactUpdates-test.js index 816ec2e053..7ca5953936 100644 --- a/src/core/__tests__/ReactUpdates-test.js +++ b/src/core/__tests__/ReactUpdates-test.js @@ -629,6 +629,43 @@ describe('ReactUpdates', function() { ]); }); + it('should flush updates in the correct order across roots', function() { + var instances = []; + var updates = []; + + var MockComponent = React.createClass({ + render: function() { + updates.push(this.props.depth); + return
; + }, + componentDidMount: function() { + instances.push(this); + if (this.props.depth < this.props.count) { + React.renderComponent( + , + this.getDOMNode() + ); + } + } + }); + + ReactTestUtils.renderIntoDocument(); + + expect(updates).toEqual([0, 1, 2]); + + ReactUpdates.batchedUpdates(function() { + // Simulate update on each component from top to bottom. + instances.forEach(function(instance) { + instance.forceUpdate(); + }); + }); + + expect(updates).toEqual([0, 1, 2, 0, 1, 2]); + }); + it('should queue nested updates', function() { // See https://github.com/facebook/react/issues/1147