diff --git a/src/diff/children.js b/src/diff/children.js index ba2730478c..fe04b5fed6 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -397,9 +397,13 @@ function findMatchingIndex( // remainingOldChildren > 1 if the oldVNode is not already used/matched. Else // if the oldVNode was null or matched, then there could needs to be at least // 1 (aka `remainingOldChildren > 0`) children to find and compare against. + // + // If there is an unkeyed functional VNode, that isn't a built-in like our Fragment, + // we should not search as we risk re-using state of an unrelated VNode. let shouldSearch = + (typeof type !== 'function' || type === Fragment || key) && remainingOldChildren > - (oldVNode != null && (oldVNode._flags & MATCHED) === 0 ? 1 : 0); + (oldVNode != null && (oldVNode._flags & MATCHED) === 0 ? 1 : 0); if ( oldVNode === null || diff --git a/test/browser/render.test.js b/test/browser/render.test.js index 96bef10bb0..cac7c7c255 100644 --- a/test/browser/render.test.js +++ b/test/browser/render.test.js @@ -1786,6 +1786,77 @@ describe('render()', () => { ); }); + // #2949 + it('should not swap unkeyed chlildren', () => { + class X extends Component { + constructor(props) { + super(props); + this.name = props.name; + } + render() { + return
{this.name}
; + } + } + + function Foo({ condition }) { + return ( +{this.name}
; + } + } + + function Foo({ condition }) { + // We swap the prop from A to B but we don't expect this to + // reflect in text-content as we are testing whether the + // state is retained for a skew that matches the original children. + // + // We insert which should amount to a skew of -1 which should + // make us correctly match the X component. + return condition ? ( +