diff --git a/src/renderers/shared/stack/reconciler/ReactChildReconciler.js b/src/renderers/shared/stack/reconciler/ReactChildReconciler.js index ee1ce8217a161..de97da800fa2b 100644 --- a/src/renderers/shared/stack/reconciler/ReactChildReconciler.js +++ b/src/renderers/shared/stack/reconciler/ReactChildReconciler.js @@ -93,8 +93,11 @@ var ReactChildReconciler = { updateChildren: function( prevChildren, nextChildren, + mountImages, removedNodes, transaction, + hostParent, + hostContainerInfo, context) { // We currently don't have a way to track moves here but if we use iterators // instead of for..in we can zip the iterators and check if an item has @@ -127,6 +130,16 @@ var ReactChildReconciler = { // The child must be instantiated before it's mounted. var nextChildInstance = instantiateReactComponent(nextElement); nextChildren[name] = nextChildInstance; + // Creating mount image now ensures refs are resolved in right order + // (see https://github.com/facebook/react/pull/7101 for explanation). + var nextChildMountImage = ReactReconciler.mountComponent( + nextChildInstance, + transaction, + hostParent, + hostContainerInfo, + context + ); + mountImages.push(nextChildMountImage); } } // Unmount children that are no longer present. diff --git a/src/renderers/shared/stack/reconciler/ReactMultiChild.js b/src/renderers/shared/stack/reconciler/ReactMultiChild.js index 96085235c78b1..0e59d32d948aa 100644 --- a/src/renderers/shared/stack/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/stack/reconciler/ReactMultiChild.js @@ -207,6 +207,7 @@ var ReactMultiChild = { _reconcilerUpdateChildren: function( prevChildren, nextNestedChildrenElements, + mountImages, removedNodes, transaction, context @@ -221,14 +222,28 @@ var ReactMultiChild = { ReactCurrentOwner.current = null; } ReactChildReconciler.updateChildren( - prevChildren, nextChildren, removedNodes, transaction, context + prevChildren, + nextChildren, + mountImages, + removedNodes, + transaction, + this, + this._hostContainerInfo, + context ); return nextChildren; } } nextChildren = flattenChildren(nextNestedChildrenElements); ReactChildReconciler.updateChildren( - prevChildren, nextChildren, removedNodes, transaction, context + prevChildren, + nextChildren, + mountImages, + removedNodes, + transaction, + this, + this._hostContainerInfo, + context ); return nextChildren; }, @@ -334,9 +349,11 @@ var ReactMultiChild = { _updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; + var mountImages = []; var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, + mountImages, removedNodes, transaction, context @@ -348,8 +365,10 @@ var ReactMultiChild = { var name; // `nextIndex` will increment for each child in `nextChildren`, but // `lastIndex` will be the last index visited in `prevChildren`. - var lastIndex = 0; var nextIndex = 0; + var lastIndex = 0; + // `nextMountIndex` will increment for each newly mounted child. + var nextMountIndex = 0; var lastPlacedNode = null; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { @@ -375,12 +394,14 @@ var ReactMultiChild = { updates, this._mountChildAtIndex( nextChild, + mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); + nextMountIndex++; } nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); @@ -468,17 +489,11 @@ var ReactMultiChild = { */ _mountChildAtIndex: function( child, + mountImage, afterNode, index, transaction, context) { - var mountImage = ReactReconciler.mountComponent( - child, - transaction, - this, - this._hostContainerInfo, - context - ); child._mountIndex = index; return this.createChild(child, afterNode, mountImage); }, diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChildReconcile-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChildReconcile-test.js index a91ca66ece65d..034d839b0fc0c 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChildReconcile-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChildReconcile-test.js @@ -60,6 +60,14 @@ var StatusDisplay = React.createClass({ return this.state.internalState; }, + componentDidMount: function() { + this.props.onFlush(); + }, + + componentDidUpdate: function() { + this.props.onFlush(); + }, + render: function() { return (
@@ -74,35 +82,61 @@ var StatusDisplay = React.createClass({ */ var FriendsStatusDisplay = React.createClass({ /** - * Retrieves the rendered children in a nice format for comparing to the input - * `this.props.usernameToStatus`. Gets the order directly from each rendered - * child's `index` field. Refs are not maintained in the rendered order, and - * neither is `this._renderedChildren` (surprisingly). - */ - getStatusDisplays: function() { - var name; - var orderOfUsernames = []; + * Gets the order directly from each rendered child's `index` field. + * Refs are not maintained in the rendered order, and neither is + * `this._renderedChildren` (surprisingly). + */ + getOriginalKeys: function() { + var originalKeys = []; // TODO: Update this to a better test that doesn't rely so much on internal // implementation details. var statusDisplays = ReactInstanceMap.get(this) ._renderedComponent ._renderedChildren; + var name; for (name in statusDisplays) { var child = statusDisplays[name]; var isPresent = !!child; if (isPresent) { - orderOfUsernames[child._mountIndex] = getOriginalKey(name); + originalKeys[child._mountIndex] = getOriginalKey(name); } } + return originalKeys; + }, + /** + * Retrieves the rendered children in a nice format for comparing to the input + * `this.props.usernameToStatus`. + */ + getStatusDisplays: function() { var res = {}; var i; - for (i = 0; i < orderOfUsernames.length; i++) { - var key = orderOfUsernames[i]; + var originalKeys = this.getOriginalKeys(); + for (i = 0; i < originalKeys.length; i++) { + var key = originalKeys[i]; res[key] = this.refs[key]; } return res; }, + /** + * Verifies that by the time a child is flushed, the refs that appeared + * earlier have already been resolved. + * TODO: This assumption will likely break with incremental reconciler + * but our internal layer API depends on this assumption. We need to change + * it to be more declarative before making ref resolution indeterministic. + */ + verifyPreviousRefsResolved: function(flushedKey) { + var i; + var originalKeys = this.getOriginalKeys(); + for (i = 0; i < originalKeys.length; i++) { + var key = originalKeys[i]; + if (key === flushedKey) { + // We are only interested in children up to the current key. + return; + } + expect(this.refs[key]).toBeTruthy(); + } + }, render: function() { var children = []; var key; @@ -110,7 +144,12 @@ var FriendsStatusDisplay = React.createClass({ var status = this.props.usernameToStatus[key]; children.push( !status ? null : - + ); } return (