Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unkeyed children diffing error causes component lifecycle event to fire unexpectedly #40

Closed
yaodingyd opened this issue Feb 6, 2018 · 1 comment · Fixed by #44
Closed

Comments

@yaodingyd
Copy link
Contributor

yaodingyd commented Feb 6, 2018

test case

Click two buttons
Expected: console log should only has "updated"
Results: Console log gets "mounted" and "unmounted".

Basically, if parent component B has unkeyed children, and there are children add/remove before child component A, even though the changed child is not a component A, A still gets mounted/unmounted, because unkeyed children are diffed in sequential order.

In the Inferno way, it has normalized VNode so if key is not set, it might use children order and null is considered a child too, so child component always has the same normalized key, thus gets the correct diffing results.

@yaodingyd yaodingyd changed the title unkeyed children diffing error cause component lifecycle event to fire unexpectedly Feb 6, 2018
@yuche yuche closed this as completed in 218563a Feb 26, 2018
@yuche
Copy link
Contributor

yuche commented Feb 26, 2018

Thanks for reporting this issue.
Your test case can be found in:

it('unkeyed children diffing error', () => {
class A extends Component {
render () {
return <div> this is a component</div>
}
componentWillUnmount () {
// console.log('unmount')
}
componentDidMount () {
// console.log('didMount')
}
componentDidUpdate () {
// console.log('didUpdate')
}
}
const cwu = sinon.spy(A.prototype, 'componentWillUnmount')
const cdm = sinon.spy(A.prototype, 'componentDidMount')
const cdu = sinon.spy(A.prototype, 'componentDidUpdate')
class B extends Component {
render () {
const visible = this.props.visible
return (
<div>
{visible ? <div>this is a plain div</div> : null}
<div>
<A />
</div>
</div>
)
}
}
let inst
class App extends Component {
state = { visible: false }
setVisible (visible) {
this.setState({
visible
})
}
constructor () {
super(...arguments)
inst = this
}
render () {
return (
<div>
<B visible={this.state.visible} />
<button onClick={this.setVisible.bind(this, true)}>yes</button>
<button onClick={this.setVisible.bind(this, false)}>no</button>
</div>
)
}
}
render(<App />, scratch)
expect(cdm.callCount).toBe(1)
inst.setVisible(true)
inst.forceUpdate()
expect(cwu.called).toBe(false)
expect(cdm.callCount).toBe(1)
expect(cdu.callCount).toBe(1)
inst.setVisible(false)
inst.forceUpdate()
expect(cwu.called).toBe(false)
expect(cdm.callCount).toBe(1)
expect(cdu.callCount).toBe(2)
inst.setVisible(true)
inst.forceUpdate()
expect(cwu.called).toBe(false)
expect(cdm.callCount).toBe(1)
expect(cdu.callCount).toBe(3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants