Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,25 @@ function constructNewChildrenArray(

if (isMounting) {
if (matchingIndex == -1) {
skew--;
// When the array of children is growing we need to decrease the skew
// as we are adding a new element to the array.
// Example:
// [1, 2, 3] --> [0, 1, 2, 3]
// oldChildren newChildren
//
// The new element is at index 0, so our skew is 0,
// we need to decrease the skew as we are adding a new element.
// The decrease will cause us to compare the element at position 1
// with value 1 with the element at position 0 with value 0.
//
// A linear concept is applied when the array is shrinking,
// if the length is unchanged we can assume that no skew
// changes are needed.
if (newChildrenLength > oldChildrenLength) {
skew--;
} else if (newChildrenLength < oldChildrenLength) {
skew++;
}
}

// If we are mounting a DOM VNode, mark it for insertion
Expand Down Expand Up @@ -407,7 +425,7 @@ function findMatchingIndex(
(oldVNode != NULL && (oldVNode._flags & MATCHED) == 0 ? 1 : 0);

if (
oldVNode === NULL ||
(oldVNode === NULL && childVNode.key == null) ||
(oldVNode &&
key == oldVNode.key &&
type === oldVNode.type &&
Expand Down
181 changes: 181 additions & 0 deletions test/browser/keys.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -943,4 +943,185 @@ describe('keys', () => {

expect(scratch.innerHTML).to.eq(`<div>${expected(sorted)}</div>`);
});

it('should handle keyed replacements', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return (
<div>
<Comp key={props.y} i={1} />
{false}
<Comp i={2} />
<Comp i={3} />
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal([
'mounted 1',
'mounted 2',
'mounted 3',
'mounted 1'
]);
});

it('should handle hole prepend', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return props.y === '2' ? (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
</div>
) : (
<div>
{null}
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);
});

it('should handle hole replace', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return props.y === '1' ? (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
</div>
) : (
<div>
<Comp key={1} i={1} />
{null}
<Comp key={3} i={3} />
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="1" />, scratch);
expect(actions).to.deep.equal([
'mounted 1',
'mounted 2',
'mounted 3',
'mounted 2'
]);
});

it('should handle hole insert', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return props.y === '2' ? (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
</div>
) : (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
{null}
<Comp key={3} i={3} />
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);
});

it('should handle hole apppend', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return props.y === '2' ? (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
</div>
) : (
<div>
<Comp key={1} i={1} />
<Comp key={2} i={2} />
<Comp key={3} i={3} />
{null}
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);
});
});
29 changes: 29 additions & 0 deletions test/browser/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1967,4 +1967,33 @@ describe('render()', () => {
'<head><title>Test</title></head><body><p>Test</p></body>\n'
);
});

it('should not remount components when replacing a component with a falsy value in-between', () => {
const actions = [];
class Comp extends Component {
componentDidMount() {
actions.push('mounted ' + this.props.i);
}
render() {
return <div>Hello</div>;
}
}

const App = props => {
return (
<div>
{props.y === '1' ? <Comp i={1} /> : <div />}
{false}
<Comp i={2} />
<Comp i={3} />
</div>
);
};

render(<App y="1" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);

render(<App y="2" />, scratch);
expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']);
});
});
Loading