diff --git a/src/diff/children.js b/src/diff/children.js index 639d254ddc..cc450b435c 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -227,9 +227,11 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) { childVNode = newParentVNode._children[i] = childVNode; } + const skewedIndex = i + skew; + // Handle unmounting null placeholders, i.e. VNode => null in unkeyed children if (childVNode == null) { - oldVNode = oldChildren[i]; + oldVNode = oldChildren[skewedIndex]; if ( oldVNode && oldVNode.key == null && @@ -250,7 +252,7 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) { // to unmount this VNode again seeing `_match==true`. Further, // getDomSibling doesn't know about _match and so would incorrectly // assume DOM nodes in this subtree are mounted and usable. - oldChildren[i] = null; + oldChildren[skewedIndex] = null; remainingOldChildren--; } continue; @@ -259,7 +261,6 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) { childVNode._parent = newParentVNode; childVNode._depth = newParentVNode._depth + 1; - const skewedIndex = i + skew; const matchingIndex = findMatchingIndex( childVNode, oldChildren, diff --git a/test/browser/render.test.js b/test/browser/render.test.js index 8f5ed0e204..ae42ce2720 100644 --- a/test/browser/render.test.js +++ b/test/browser/render.test.js @@ -1481,6 +1481,60 @@ describe('render()', () => { expect(serializeHtml(scratch)).to.equal( '

_B1

_B2

_B3

_B4

_B5

_B6

_B7

_B8

_B9

_B10

_B11

_B12

_B13

' + ); + }); + + it('should not crash or repeatedly add the same child when replacing a matched vnode with null (mixed dom-types)', () => { + const B = () =>
B
; + + /** @type {() => void} */ + let update; + class App extends Component { + constructor(props) { + super(props); + this.state = { show: true }; + update = () => { + this.setState(state => ({ show: !state.show })); + }; + } + + render() { + if (this.state.show) { + return ( +
+ +
C
+
+ ); + } + return ( +
+ A + {null} + +
C
+
+ ); + } + } + + render(, scratch); + expect(scratch.innerHTML).to.equal('
B
C
'); + + update(); + rerender(); + expect(scratch.innerHTML).to.equal( + '
A
B
C
' + ); + + update(); + rerender(); + expect(scratch.innerHTML).to.equal('
B
C
'); + + update(); + rerender(); + expect(scratch.innerHTML).to.equal( + '
A
B
C
' ); }); });