Change:
This throws . Maybe this can be simplified some more but this was the simplest repro I could make. Thanks for sending a repro case over.
Wow, it really was a bug. Narrowed it down a little, but it's still rediculously "complex". Big props to for repro! Anyway, just glancing at the code and from experience, I can safely assume that the issue is that receives multiple updates to the same node from . Which intuitively didn't make any sense at all to me, since always calls when it's done and is basically just recursive. However, when taking another peek at the code, I'm pretty sure that the issue is that (in this case) makes the invalid assumption that all updates belongs to the same root. When that is not the case, an to a component which itself updates another root will only once everything is done. The updates to the other root will NOT after each on that root because will always greater than 0. In practice this means that whenever updates another root, all DOM updates on that root will be batched one after the other (as opposed to batching component updates) which is a violation of assumptions in . It's also very inefficient. Intuitively it seems that what should happen is that all component updates within a call should be (but that is not for sure), exactly how this should be solved in practice I'm not 100% sure right now. But I'm 95% sure that this is what's happening, and the repro does have all the ingredients.
Speaking a bit to in chat, I feel like the issue is that and when called from within become asynchronous as opposed to synchronous as they usually are. Which leads to this issue, but also possibly a lot of other subtle issues.
Apparently is not at all to blame...
Narrowed it down even further, apparently it breaks immediately on the first , the error just didn't show up until later unless prodded. I still don't feel any more enlightened on the issue though...
Your latest two jsbins (4 & 6) throw different error for me (from in X ): And, if one removes Z from A , the same X throws , because then A render won't have any refs on that render so will be undefined... React docs warn that "Never access refs inside of any component's render method - or while any component's render method is even running anywhere in the call stack.", don't know if it's relevant here.
Yeah different error, but I'm sure the cause is the same. The issue is the span not actually ever being rendered, just that in the old repro it doesn't actually turn into an error until later.
Ok, so perhaps here's a reduced test case, see console: is being called twice in a row, and the first time the span is not there in DOM even though it has been rendered already. One culprit for this behavior may be that you are effectively calling setState on sibling component where setState would not be allowed on component itself (triggered via componentWillUpdate).
Huh? It does end up being rendered in your test case, and there's no error?
no I didn't want to throw the error so that you can see that the componentDidUpdate will be called double. The point is that first the span is not there and if one would try to access it (as in your ref.getDOMNode()) it would throw. See console for flow.
is defined so it's an error regardless, and more than that, React expects the node to be there, but it isn't and throws an error. Even after it still isn't physically there in the DOM. So something is wrong. Unless I'm missing something.
Yes, there is definitely a problem there as you said, sorry for not being clear. I just wanted to reduce it so that there are no refs or forceUpdates which may bring other problems. One could throw the error in componentDidUpdate for clarity, as the innerHTML is definitely empty where it should not be. But it is interesting that it will be called right away again, and then the span will be there.

Discussion:
* Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. */ <del> batchedUpdates: function(callback, param) { callback(param); </del> <ins> batchedUpdates: function(callback, a, b) { callback(a, b); </ins> } };

* Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. */ <del> batchedUpdates: function(callback, param) { </del> <ins> batchedUpdates: function(callback, a, b) { </ins> var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // The code is written this way to avoid extra allocations if (alreadyBatchingUpdates) { <del> callback(param); </del> <ins> callback(a, b); </ins> } else { <del> transaction.perform(callback, null, param); </del> <ins> transaction.perform(callback, null, a, b); </ins> } } };

"use strict"; <ins> var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); </ins> var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactPerf = require('ReactPerf'); <ins> var Transaction = require('Transaction'); </ins> var invariant = require('invariant'); <ins> var mixInto = require('mixInto'); </ins> var warning = require('warning'); var dirtyComponents = [];

); } <del> function batchedUpdates(callback, param) { </del> <ins> var NESTED_UPDATES = { initialize: function() { this.dirtyComponentsLength = dirtyComponents.length; }, close: function() { if (this.dirtyComponentsLength !== dirtyComponents.length) { // Additional updates were enqueued by componentDidUpdate handlers or // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run // these new updates so that if A's componentDidUpdate calls setState on // B, B will update before the callback A's updater provided when calling // setState. dirtyComponents.splice(0, this.dirtyComponentsLength); flushBatchedUpdates(); } else { dirtyComponents.length = 0; } } }; var UPDATE_QUEUEING = { initialize: function() { this.callbackQueue.reset(); }, close: function() { this.callbackQueue.notifyAll(); } }; var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING]; function ReactUpdatesFlushTransaction() { this.reinitializeTransaction(); this.dirtyComponentsLength = null; this.callbackQueue = CallbackQueue.getPooled(null); this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(); } mixInto(ReactUpdatesFlushTransaction, Transaction.Mixin); mixInto(ReactUpdatesFlushTransaction, { getTransactionWrappers: function() { return TRANSACTION_WRAPPERS; }, destructor: function() { this.dirtyComponentsLength = null; CallbackQueue.release(this.callbackQueue); this.callbackQueue = null; ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction); this.reconcileTransaction = null; }, perform: function(method, scope, a) { // Essentially calls `this.reconcileTransaction.perform(method, scope, a)` // with this transaction's wrappers around it. return Transaction.Mixin.perform.call( this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a ); } }); PooledClass.addPoolingTo(ReactUpdatesFlushTransaction); function batchedUpdates(callback, a, b) { </ins> ensureInjected(); <del> batchingStrategy.batchedUpdates(callback, param); </del> <ins> batchingStrategy.batchedUpdates(callback, a, b); </ins> } /**

} function runBatchedUpdates(transaction) { <ins> var len = transaction.dirtyComponentsLength; invariant( len === dirtyComponents.length, 'Expected flush transaction's stored dirty-components length (%s) to ' + 'match dirty-components array length (%s).', len, dirtyComponents.length ); </ins> // 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); <del> for (var i = 0; i < dirtyComponents.length; i++) { </del> <ins> for (var i = 0; i < len; i++) { </ins> // If a component is unmounted before pending changes apply, ignore them // TODO: Queue unmounts in the same list to avoid this happening at all var component = dirtyComponents[i];

// stash the callbacks first var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; <del> component.performUpdateIfNecessary(transaction); </del> <ins> component.performUpdateIfNecessary(transaction.reconcileTransaction); </ins> if (callbacks) { for (var j = 0; j < callbacks.length; j++) { <del> transaction.getReactMountReady().enqueue( </del> <ins> transaction.callbackQueue.enqueue( </ins> callbacks[j], component );

} } <del> function clearDirtyComponents() { dirtyComponents.length = 0; } function flushBatchedUpdatesOnce(transaction) { // Run these in separate functions so the JIT can optimize try { runBatchedUpdates(transaction); } finally { clearDirtyComponents(); } } </del> var flushBatchedUpdates = ReactPerf.measure( 'ReactUpdates', 'flushBatchedUpdates', function() { <del> // flushBatchedUpdatesOnce will clear the dirtyComponents array, but // mount-ready handlers (i.e., componentDidMount/Update) may enqueue more // state updates which we should apply immediately </del> <ins> // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents // array and perform any updates enqueued by mount-ready handlers (i.e., // componentDidUpdate) but we need to check here too in order to catch // updates enqueued by setState callbacks. </ins> while (dirtyComponents.length) { <del> var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform(flushBatchedUpdatesOnce, null, transaction); ReactUpdates.ReactReconcileTransaction.release(transaction); </del> <ins> var transaction = ReactUpdatesFlushTransaction.getPooled(); transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction); </ins> } } );

); if (!batchingStrategy.isBatchingUpdates) { <del> var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform( component.performUpdateIfNecessary, component, transaction ); ReactUpdates.ReactReconcileTransaction.release(transaction); callback && callback.call(component); </del> <ins> batchingStrategy.batchedUpdates(enqueueUpdate, component, callback); </ins> return; }

expect(aUpdated).toBe(true); }); <ins> it('should flush updates in the correct order', function() { var updates = []; var Outer = React.createClass({ getInitialState: function() { return {x: 0}; }, render: function() { updates.push('Outer-render-' + this.state.x); return <div><Inner x={this.state.x} ref="inner" /></div>; }, componentDidUpdate: function() { var x = this.state.x; updates.push('Outer-didUpdate-' + x); updates.push('Inner-setState-' + x); this.refs.inner.setState({x: x}, function() { updates.push('Inner-callback-' + x); }); } }); var Inner = React.createClass({ getInitialState: function() { return {x: 0}; }, render: function() { updates.push('Inner-render-' + this.props.x + '-' + this.state.x); return <div />; }, componentDidUpdate: function() { updates.push('Inner-didUpdate-' + this.props.x + '-' + this.state.x); } }); var instance = ReactTestUtils.renderIntoDocument(<Outer />); updates.push('Outer-setState-1'); instance.setState({x: 1}, function() { updates.push('Outer-callback-1'); updates.push('Outer-setState-2'); instance.setState({x: 2}, function() { updates.push('Outer-callback-2'); }); }); expect(updates).toEqual([ 'Outer-render-0', 'Inner-render-0-0', 'Outer-setState-1', 'Outer-render-1', 'Inner-render-1-0', 'Inner-didUpdate-1-0', 'Outer-didUpdate-1', 'Inner-setState-1', 'Inner-render-1-1', 'Inner-didUpdate-1-1', 'Inner-callback-1', 'Outer-callback-1', 'Outer-setState-2', 'Outer-render-2', 'Inner-render-2-1', 'Inner-didUpdate-2-1', 'Outer-didUpdate-2', 'Inner-setState-2', 'Inner-render-2-2', 'Inner-didUpdate-2-2', 'Inner-callback-2', 'Outer-callback-2' ]); }); it('should queue nested updates', function() { // See https://github.com/facebook/react/issues/1147 var X = React.createClass({ getInitialState: function() { return {s: 0}; }, render: function() { if (this.state.s === 0) { return <div> <span>0</span> </div>; } else { return <div>1</div>; } }, go: function() { this.setState({s: 1}); this.setState({s: 0}); this.setState({s: 1}); } }); var Y = React.createClass({ render: function() { return <div> <Z /> </div>; } }); var Z = React.createClass({ render: function() { return <div />; }, componentWillUpdate: function() { x.go(); } }); var x; var y; x = ReactTestUtils.renderIntoDocument(<X />); y = ReactTestUtils.renderIntoDocument(<Y />); expect(x.getDOMNode().textContent).toBe('0'); y.forceUpdate(); expect(x.getDOMNode().textContent).toBe('1'); }); it('should queue updates from during mount', function() { // See https://github.com/facebook/react/issues/1353 var a; var A = React.createClass({ getInitialState: function() { return {x: 0}; }, componentWillMount: function() { a = this; }, render: function() { return <div>A{this.state.x}</div>; } }); var B = React.createClass({ componentWillMount: function() { a.setState({x: 1}); }, render: function() { return <div />; } }); ReactUpdates.batchedUpdates(function() { ReactTestUtils.renderIntoDocument( <div> <A /> <B /> </div> ); }); expect(a.state.x).toBe(1); expect(a.getDOMNode().textContent).toBe('A1'); }); it('calls componentWillReceiveProps setState callback properly', function() { var callbackCount = 0; var A = React.createClass({ getInitialState: function() { return {x: this.props.x}; }, componentWillReceiveProps: function(nextProps) { var newX = nextProps.x; this.setState({x: newX}, function() { // State should have updated by the time this callback gets called expect(this.state.x).toBe(newX); callbackCount++; }); }, render: function() { return <div>{this.state.x}</div>; } }); var container = document.createElement('div'); React.renderComponent(<A x={1} />, container); React.renderComponent(<A x={2} />, container); expect(callbackCount).toBe(1); }); </ins> });