Hao
Hao

Reputation: 1554

Why ReactCSSTransitionGroup generates multiple components though I expect only one?

I am using ReactCSSTransitionGroup to do some animation and I found an interesting thing which does not make any sense to me.

In the example below, when I click <div className="HeartControl">, it will update the height of the <div className="HeartFill"> which works fine. (I know to achieve the effect does not necessarily need ReactCSSTransitionGroup here though).

Interesting thing is that when I click, there will be another <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div> with a new React component id added after the existing one.

But I expect there will always be only ONE <div className="HeartFill"> there.

Why this happened???

P.S.. after a few clicks, the result will look like:

  <span data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2">
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3.5999999999999996px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$6:0"></div>
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$5:0"></div>

    var HEIGHT_HEART = 30;
   var NUM_HEART_MAX = 50;

   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;


  var Heart = React.createClass({

getInitialState: function() {
    return {
        heartHeight: 0
    };
},

onClick: function(e) {
    var currentHeartHeight = this.state.heartHeight;
    this.setState({
        heartHeight: currentHeartHeight + 1
    });
},

render: function() {

    var styleHeartFill = {
        'position': 'absolute',
        'bottom': 0,
        'left': 0,
        'width': 30,
        'height': this.state.heartHeight / NUM_HEART_MAX * HEIGHT_HEART,
        'background-color': '#D64541'
    };


    return (
        <div className="Heart" >
                <div className="HeartControl" onClick={this.onClick}>
                    <i className="fa fa-angle-up" />
                </div>
            <img src="heart.png" className="HeartOutline" />
            <ReactCSSTransitionGroup transitionName="HeartFill">
                <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>
            </ReactCSSTransitionGroup>
        </div>
    );
 }

     });
    React.renderComponent(<Heart />, document.getElementById('Heart'));

`

Upvotes: 3

Views: 3984

Answers (5)

pinkninja
pinkninja

Reputation: 119

I used gluxon's advice as a starting point - what worked for me was removing the leave transition and making it display nothing:

.example-leave.example-leave-active { display: none; }

Upvotes: 0

gluxon
gluxon

Reputation: 770

I'm fairly late to the game with this answer, but I ran into this issue as well and want to provide a solution for others. Removing the key is not a sufficient solution since React relies on it to know when to animate items. The documentation now has a section discouraging this.

You must provide the key attribute for all children of ReactCSSTransitionGroup, even when only rendering a single item. This is how React will determine which children have entered, left, or stayed. - https://facebook.github.io/react/docs/animation.html

If you are only animating the entry/exit of a single item, a CSS hack can be used to fix flickering that may be seen from multiple items entering/exiting.

.HeartFill {
    display: none;
}
.HeartFill:first-child {
    display: block;
}

React will add new elements on top in most cases, but this isn't guaranteed. If your transitionEndTimeout prop is set to a relatively short time, this shouldn't be a huge concern. The timeout prop should also match the CSS transition time.

Upvotes: 1

errnesto
errnesto

Reputation: 832

ReactCSSTransitionGroup should create a second element

it will remove it when a specified css animation has finished or print a warning when there is no animation in the css

maybe look at the Low-level API for better understanding: http://facebook.github.io/react/docs/animation.html (bottom of the page)

Upvotes: 0

Naman Goel
Naman Goel

Reputation: 1612

Here is the problem:

You are providing a value for key which is changing over time. Keys are used to decide if an element is the same or different.

<div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>

When you do this the value for key changes and React thinks a new element is entering and an old element is leaving.

Usually you need a unique key which can either be sequential or be generated using Math.random(). (remember to generate them once with getInitialState or DefaultProps, not in render, as that would create a new key every time).

The order of elements is another thing that can be in trouble.

From React's documentation:

In practice browsers will preserve property order except for properties that can be
parsed as a 32-bit unsigned integers. Numeric properties will be ordered sequentially
and before other properties. If this happens React will render components out of
order. This can be avoided by adding a string prefix to the key:

Upvotes: 0

user3508122
user3508122

Reputation: 674

I suspect the reason your getting more than one is because your using the key prop

<div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>

From React docs http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).

Heres a jsfiddle using the key prop http://jsfiddle.net/kb3gN/3826/

Heres a jsfiddle not using the key prop http://jsfiddle.net/kb3gN/3827/

P.s I've made a few changes in the fiddle just to try and better demostrate the reasoning

Upvotes: 2

Related Questions