Delete child when the key lines up but the type doesn't (#8085)

When keys line up in the beginning but the type doesn't
we don't currently delete the child. We need to track that this
fiber is a not a reuse and if so mark the old one for deletion.
This commit is contained in:
Sebastian Markbåge
2016-10-25 07:51:53 -04:00
committed by Dan Abramov
parent 225325eada
commit ea8cf7fbff
2 changed files with 126 additions and 1 deletions
@@ -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
);
}
}
@@ -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 <span prop="Class" />;
}
}
function FunctionalComponent(props) {
return <span prop="Function" />;
}
function Foo(props) {
return (
<div>
{props.useClass ?
<ClassComponent /> :
props.useFunction ?
<FunctionalComponent /> :
props.useText ?
'Text' :
null
}
Trail
</div>
);
}
ReactNoop.render(<Foo useClass={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div(span('Class'), 'Trail'),
]);
expect(unmounted).toBe(false);
ReactNoop.render(<Foo useFunction={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div(span('Function'), 'Trail'),
]);
expect(unmounted).toBe(true);
ReactNoop.render(<Foo useText={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div('Text', 'Trail'),
]);
ReactNoop.render(<Foo />);
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 <span prop="Class" />;
}
}
function FunctionalComponent(props) {
return <span prop="Function" />;
}
function Foo(props) {
return (
<div>
{props.useClass ?
<ClassComponent key="a" /> :
props.useFunction ?
<FunctionalComponent key="a" /> :
null
}
Trail
</div>
);
}
ReactNoop.render(<Foo useClass={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div(span('Class'), 'Trail'),
]);
expect(unmounted).toBe(false);
ReactNoop.render(<Foo useFunction={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div(span('Function'), 'Trail'),
]);
expect(unmounted).toBe(true);
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
div('Trail'),
]);
});
it('does not update child nodes if a flush is aborted', () => {
function Bar(props) {