From cf86cd8ebfab28d01e5a2b8dae00e7993fd9d7cc Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 30 Sep 2015 16:43:49 -0700 Subject: [PATCH] Fix dev/prod mismatch in context === bailout Fixes #5005. --- src/renderers/dom/shared/ReactDOMComponent.js | 42 ++++++++++++++----- .../__tests__/ReactCompositeComponent-test.js | 23 ++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 5114182919..e1e5695d17 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]; @@ -719,7 +727,7 @@ ReactDOMComponent.Mixin = { var mountImages = this.mountChildren( childrenToUse, transaction, - processChildContext(context, this) + context ); ret = mountImages.join(''); } @@ -759,7 +767,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]); @@ -821,13 +829,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;