diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index af4af15404566..f51a00ba011e4 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -508,6 +508,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { // boolean, undefined, etc. break; } + if (shouldTrackSideEffects) { + if (oldFiber && !newFiber.alternate) { + // We matched the slot, but we didn't reuse the existing fiber, so we + // need to delete the existing child. + deleteChild(returnFiber, oldFiber); + } + } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (!previousNewFiber) { // TODO: Move out of the loop. This only happens for the first run. @@ -573,7 +580,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { // it from the child list so that we don't add it to the deletion // list. existingChildren.delete( - newFiber.key === null ? newFiber.index : newFiber.key + newFiber.key === null ? newIdx : newFiber.key ); } } diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js index 897422d2274d2..ab8f7a55d7cba 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js @@ -164,6 +164,124 @@ describe('ReactIncrementalSideEffects', () => { }); + it('can delete a child that changes type - implicit keys', function() { + + let unmounted = false; + + class ClassComponent extends React.Component { + componentWillUnmount() { + unmounted = true; + } + render() { + return ; + } + } + + function FunctionalComponent(props) { + return ; + } + + function Foo(props) { + return ( +
+ {props.useClass ? + : + props.useFunction ? + : + props.useText ? + 'Text' : + null + } + Trail +
+ ); + } + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div(span('Class'), 'Trail'), + ]); + + expect(unmounted).toBe(false); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div(span('Function'), 'Trail'), + ]); + + expect(unmounted).toBe(true); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div('Text', 'Trail'), + ]); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div('Trail'), + ]); + + }); + + it('can delete a child that changes type - explicit keys', function() { + + let unmounted = false; + + class ClassComponent extends React.Component { + componentWillUnmount() { + unmounted = true; + } + render() { + return ; + } + } + + function FunctionalComponent(props) { + return ; + } + + function Foo(props) { + return ( +
+ {props.useClass ? + : + props.useFunction ? + : + null + } + Trail +
+ ); + } + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div(span('Class'), 'Trail'), + ]); + + expect(unmounted).toBe(false); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div(span('Function'), 'Trail'), + ]); + + expect(unmounted).toBe(true); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.root.children).toEqual([ + div('Trail'), + ]); + + }); + it('does not update child nodes if a flush is aborted', () => { function Bar(props) {