From 8760a70e735cd7afcf70108c133d7df81fda3ef3 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 12 Nov 2014 21:27:11 -0500 Subject: [PATCH] Adding "appear" phase to ReactTransitionGroup and ReactCSSTransitionGroup. "appear" differs from "enter" in that all children of a transition group at mount time will "appear" but will not "enter". All children later added to an existing transition group will "enter" but not "appear". This extra transition phase allows for animation-on-mount effects. A mirroring "appear" prop has been added to ReactCSSTransitionGroup, however for reverse-compatibility (and because "appear" is less common) it defaults to false. Thanks to @afa for his work investigating the possible ways to implement this. --- .../transitions/ReactCSSTransitionGroup.js | 3 ++ .../ReactCSSTransitionGroupChild.js | 8 +++ .../transitions/ReactTransitionGroup.js | 53 ++++++++++++++++--- .../__tests__/ReactTransitionGroup-test.js | 19 ++++--- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/addons/transitions/ReactCSSTransitionGroup.js b/src/addons/transitions/ReactCSSTransitionGroup.js index 2e4190c729..dd481eef6b 100644 --- a/src/addons/transitions/ReactCSSTransitionGroup.js +++ b/src/addons/transitions/ReactCSSTransitionGroup.js @@ -28,12 +28,14 @@ var ReactCSSTransitionGroup = React.createClass({ propTypes: { transitionName: React.PropTypes.string.isRequired, + transitionAppear: React.PropTypes.bool, transitionEnter: React.PropTypes.bool, transitionLeave: React.PropTypes.bool }, getDefaultProps: function() { return { + transitionAppear: false, transitionEnter: true, transitionLeave: true }; @@ -46,6 +48,7 @@ var ReactCSSTransitionGroup = React.createClass({ return ReactCSSTransitionGroupChild( { name: this.props.transitionName, + appear: this.props.transitionAppear, enter: this.props.transitionEnter, leave: this.props.transitionLeave }, diff --git a/src/addons/transitions/ReactCSSTransitionGroupChild.js b/src/addons/transitions/ReactCSSTransitionGroupChild.js index 3feb6c78dd..6bdcf90e16 100644 --- a/src/addons/transitions/ReactCSSTransitionGroupChild.js +++ b/src/addons/transitions/ReactCSSTransitionGroupChild.js @@ -107,6 +107,14 @@ var ReactCSSTransitionGroupChild = React.createClass({ } }, + componentWillAppear: function(done) { + if (this.props.appear) { + this.transition('appear', done); + } else { + done(); + } + }, + componentWillEnter: function(done) { if (this.props.enter) { this.transition('enter', done); diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js index ce967cff4c..80296e4e48 100644 --- a/src/addons/transitions/ReactTransitionGroup.js +++ b/src/addons/transitions/ReactTransitionGroup.js @@ -39,6 +39,21 @@ var ReactTransitionGroup = React.createClass({ }; }, + componentWillMount: function() { + this.currentlyTransitioningKeys = {}; + this.keysToEnter = []; + this.keysToLeave = []; + }, + + componentDidMount: function() { + var initialChildMapping = this.state.children; + for (var key in initialChildMapping) { + if (initialChildMapping[key]) { + this.performAppear(key); + } + } + }, + componentWillReceiveProps: function(nextProps) { var nextChildMapping = ReactTransitionChildMapping.getChildMapping( nextProps.children @@ -73,12 +88,6 @@ var ReactTransitionGroup = React.createClass({ // If we want to someday check for reordering, we could do it here. }, - componentWillMount: function() { - this.currentlyTransitioningKeys = {}; - this.keysToEnter = []; - this.keysToLeave = []; - }, - componentDidUpdate: function() { var keysToEnter = this.keysToEnter; this.keysToEnter = []; @@ -89,6 +98,38 @@ var ReactTransitionGroup = React.createClass({ keysToLeave.forEach(this.performLeave); }, + performAppear: function(key) { + this.currentlyTransitioningKeys[key] = true; + + var component = this.refs[key]; + + if (component.componentWillAppear) { + component.componentWillAppear( + this._handleDoneAppearing.bind(this, key) + ); + } else { + this._handleDoneAppearing(key); + } + }, + + _handleDoneAppearing: function(key) { + var component = this.refs[key]; + if (component.componentDidAppear) { + component.componentDidAppear(); + } + + delete this.currentlyTransitioningKeys[key]; + + var currentChildMapping = ReactTransitionChildMapping.getChildMapping( + this.props.children + ); + + if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) { + // This was removed before it had fully appeared. Remove it. + this.performLeave(key); + } + }, + performEnter: function(key) { this.currentlyTransitioningKeys[key] = true; diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js index feddef8538..1a4b23fc25 100644 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js @@ -36,6 +36,13 @@ describe('ReactTransitionGroup', function() { componentDidMount: function() { log.push('didMount'); }, + componentWillAppear: function(cb) { + log.push('willAppear'); + cb(); + }, + componentDidAppear: function() { + log.push('didAppear'); + }, componentWillEnter: function(cb) { log.push('willEnter'); cb(); @@ -72,15 +79,15 @@ describe('ReactTransitionGroup', function() { }); var instance = React.render(, container); - expect(log).toEqual(['didMount']); + expect(log).toEqual(['didMount', 'willAppear', 'didAppear']); + log = []; instance.setState({count: 2}, function() { - expect(log).toEqual(['didMount', 'didMount', 'willEnter', 'didEnter']); + expect(log).toEqual(['didMount', 'willEnter', 'didEnter']); + + log = []; instance.setState({count: 1}, function() { - expect(log).toEqual([ - "didMount", "didMount", "willEnter", "didEnter", - "willLeave", "didLeave", "willUnmount" - ]); + expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']); }); }); });