diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index ff1321851a..4e30c5f699 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -480,14 +480,12 @@ function validateDangerousTag(tag) { } } -function processChildContext(context, inst) { - if (__DEV__) { - // Pass down our tag name to child components for validation purposes - context = assign({}, context); - var info = context[validateDOMNesting.ancestorInfoContextKey]; - context[validateDOMNesting.ancestorInfoContextKey] = - validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst); - } +function processChildContextDev(context, inst) { + // Pass down our tag name to child components for validation purposes + context = assign({}, context); + var info = context[validateDOMNesting.ancestorInfoContextKey]; + context[validateDOMNesting.ancestorInfoContextKey] = + validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst); return context; } @@ -519,6 +517,10 @@ function ReactDOMComponent(tag) { this._wrapperState = null; this._topLevelWrapper = null; this._nodeWithLegacyProperties = null; + if (__DEV__) { + this._unprocessedContextDev = null; + this._processedContextDev = null; + } } ReactDOMComponent.displayName = 'ReactDOMComponent'; @@ -588,6 +590,12 @@ ReactDOMComponent.Mixin = { } } + if (__DEV__) { + this._unprocessedContextDev = context; + this._processedContextDev = processChildContextDev(context, this); + context = this._processedContextDev; + } + var mountImage; if (transaction.useCreateElement) { var ownerDocument = context[ReactMount.ownerDocumentContextKey]; @@ -721,7 +729,7 @@ ReactDOMComponent.Mixin = { var mountImages = this.mountChildren( childrenToUse, transaction, - processChildContext(context, this) + context ); ret = mountImages.join(''); } @@ -761,7 +769,7 @@ ReactDOMComponent.Mixin = { var mountImages = this.mountChildren( childrenToUse, transaction, - processChildContext(context, this) + context ); for (var i = 0; i < mountImages.length; i++) { el.appendChild(mountImages[i]); @@ -823,13 +831,25 @@ ReactDOMComponent.Mixin = { break; } + if (__DEV__) { + // If the context is reference-equal to the old one, pass down the same + // processed object so the update bailout in ReactReconciler behaves + // correctly (and identically in dev and prod). See #5005. + if (this._unprocessedContextDev !== context) { + this._unprocessedContextDev = context; + this._processedContextDev = processChildContextDev(context, this); + } + context = this._processedContextDev; + } + + assertValidProps(this, nextProps); this._updateDOMProperties(lastProps, nextProps, transaction, null); this._updateDOMChildren( lastProps, nextProps, transaction, - processChildContext(context, this) + context ); if (!canDefineProperty && this._nodeWithLegacyProperties) { diff --git a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js index d0e25cbbf5..929d3e70eb 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js @@ -607,6 +607,29 @@ describe('ReactCompositeComponent', function() { expect(ReactDOM.findDOMNode(component).innerHTML).toBe('bar'); }); + it('should skip update when rerendering element in container', function() { + var Parent = React.createClass({ + render: function() { + return
{this.props.children}
; + }, + }); + + var childRenders = 0; + var Child = React.createClass({ + render: function() { + childRenders++; + return
; + }, + }); + + var container = document.createElement('div'); + var child = ; + + ReactDOM.render({child}, container); + ReactDOM.render({child}, container); + expect(childRenders).toBe(1); + }); + it('should pass context when re-rendered for static child', function() { var parentInstance = null; var childInstance = null;