diff --git a/src/diff/index.js b/src/diff/index.js index 477663cd9e..87c0ba21e4 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -134,14 +134,14 @@ export function diff( } if ( - (!c._force && - c.shouldComponentUpdate != null && + !c._force && + ((c.shouldComponentUpdate != null && c.shouldComponentUpdate( newProps, c._nextState, componentContext ) === false) || - newVNode._original === oldVNode._original + newVNode._original === oldVNode._original) ) { // More info about this here: https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8 if (newVNode._original !== oldVNode._original) { @@ -154,8 +154,6 @@ export function diff( c._dirty = false; } - // In cases of bailing due to strict-equality we have to reset force as well - c._force = false; newVNode._dom = oldVNode._dom; newVNode._children = oldVNode._children; newVNode._children.forEach(vnode => { @@ -188,6 +186,7 @@ export function diff( c.context = componentContext; c.props = newProps; c._parentDom = parentDom; + c._force = false; let renderHook = options._render, count = 0; @@ -255,8 +254,6 @@ export function diff( if (clearProcessingException) { c._pendingError = c._processingException = null; } - - c._force = false; } else if ( excessDomChildren == null && newVNode._original === oldVNode._original diff --git a/test/browser/components.test.js b/test/browser/components.test.js index 1391d52603..3d36e3a29f 100644 --- a/test/browser/components.test.js +++ b/test/browser/components.test.js @@ -2660,5 +2660,58 @@ describe('Components', () => { expect(scratch.innerHTML).to.equal('
bar
'); }); + + it('should skip shouldComponentUpdate when called during render', () => { + let isSCUCalled = false; + class App extends Component { + shouldComponentUpdate() { + isSCUCalled = true; + return false; + } + render() { + const isUpdated = this.isUpdated; + if (!isUpdated) { + this.isUpdated = true; + this.forceUpdate(); + } + return
Updated: {isUpdated ? 'yes' : 'no'}
; + } + } + render(, scratch); + rerender(); + expect(isSCUCalled).to.be.false; + expect(scratch.innerHTML).to.equal('
Updated: yes
'); + }); + + it('should break through strict equality optimization', () => { + let isSCUCalled = false; + + class Child extends Component { + componentDidMount() { + this.props.parent.forceUpdate(); + this.forceUpdate(); + this.isUpdated = true; + } + shouldComponentUpdate() { + isSCUCalled = true; + return false; + } + render() { + return
Updated: {this.isUpdated ? 'yes' : 'no'}
; + } + } + + class App extends Component { + children = (); + render() { + return this.children; + } + } + + render(, scratch); + rerender(); + expect(isSCUCalled).to.be.false; + expect(scratch.innerHTML).to.equal('
Updated: yes
'); + }); }); });