diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js index bb99b66284..501d518b1a 100644 --- a/src/addons/transitions/ReactTransitionGroup.js +++ b/src/addons/transitions/ReactTransitionGroup.js @@ -89,6 +89,8 @@ var ReactTransitionGroup = React.createClass({ }, componentDidUpdate: function() { + this.updatedChildren = null; + var keysToEnter = this.keysToEnter; this.keysToEnter = []; keysToEnter.forEach(this.performEnter); @@ -193,9 +195,13 @@ var ReactTransitionGroup = React.createClass({ // This entered again before it fully left. Add it again. this.performEnter(key); } else { - var newChildren = assign({}, this.state.children); - delete newChildren[key]; - this.setState({children: newChildren}); + // As this.state.children will not be updated until next render, we keep + // this.updatedChildren state to avoid losing all but the last removal. + // It's cleaned after this.state is updated, in componentDidUpdate. + if (!this.updatedChildren) + this.updatedChildren = assign({}, this.state.children); + delete this.updatedChildren[key]; + this.setState({children: this.updatedChildren}); } }, diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js index c1f36323d4..2e315d2818 100644 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js @@ -208,4 +208,65 @@ describe('ReactTransitionGroup', function() { 'didMount', 'didMount', 'willEnter', 'didEnter' ]); }); + + it('should handle entering/leaving several elements at once', function() { + var log = []; + var cb; + + var Child = React.createClass({ + componentDidMount: function() { + log.push('didMount'+this.props.id); + }, + componentWillEnter: function(cb) { + log.push('willEnter'+this.props.id); + cb(); + }, + componentDidEnter: function() { + log.push('didEnter'+this.props.id); + }, + componentWillLeave: function(cb) { + log.push('willLeave'+this.props.id); + cb(); + }, + componentDidLeave: function() { + log.push('didLeave'+this.props.id); + }, + componentWillUnmount: function() { + log.push('willUnmount'+this.props.id); + }, + render: function() { + return ; + } + }); + + var Component = React.createClass({ + getInitialState: function() { + return {count: 1}; + }, + render: function() { + var children = []; + for (var i = 0; i < this.state.count; i++) { + children.push(); + } + return {children}; + } + }); + + var instance = React.render(, container); + expect(log).toEqual(['didMount0']); + log = []; + + instance.setState({count: 3}); + expect(log).toEqual([ + 'didMount1', 'didMount2', 'willEnter1', 'didEnter1', + 'willEnter2', 'didEnter2' + ]); + log = []; + + instance.setState({count: 0}); + expect(log).toEqual([ + 'willLeave0', 'didLeave0', 'willLeave1', 'didLeave1', + 'willLeave2', 'didLeave2', 'willUnmount0', 'willUnmount1', 'willUnmount2' + ]); + }); });