In this post I’m going to explore a quick solution to applying React.js CSS transitions to components when they are first rendered to the DOM by creating an interface component for the ReactCSSTransitionGroup add-on.
What does the ReactCSSTransitionGroup add-on do?
The ReactCSSTransitionGroup is an add-on component for React.js which allows you to perform CSS transitions and animations when a React component enters or leaves the DOM. When a new item is added to a ReactCSSTransitionGroup it receives CSS classes to initiate an enter transition, and when it leaves the DOM it receives the corresponding classes to trigger a leaving transition.
This essentially allows you to apply whatever CSS transitions you want to elements as they enter and leave the DOM.
The problem
Recently while using the ReactCSSTransitionGroup add-on for React.js, I ran into the issue of not being able to apply the enter transitions to the child elements when the component is first rendered to the DOM.
When the component is initially rendered, both the ReactCSSTransitionGroup and all of its child elements appear in the DOM at the same time. However, the ReactCSSTransitionGroup will only apply the appropriate animation classes to any child elements which enter the DOM after the initial render.
To illustrate what I mean, here is the example from the React.js documentation implemented in CodePen.
The components inside the transition group will transition in and out of the page when added or removed as defined by the corresponding css rules, but when you first load the page they do not transition in on the initial render.
A work-around to achieve the desired effect
One way around this is to delay the rendering of child components until after the parent component has mounted to the DOM. This approach is outlined in this StackOverflow answer. This is a pretty simple, yet effective, work-around, and requires adding the following code to the component which is responsible for rendering the ReactCSSTransitionGroup component.
getInitialState: function() {
return { mounted: false };
},
componentDidMount: function() {
this.setState({ mounted: true });
},
render: function() {
var child;
if(this.state.mounted){
child = (<div> ... </div>);
}
return (
<ReactTransitionGroup transitionName="example">
{child}
</ReactTransitionGroup>
);
}
How can this solution be improved?
The conditional re-render after mounting is a fine work around, but if I want to use this effect in multiple components then I’m going to be repeating it often. Furthermore, I don’t particularly like the idea polluting the component state with an otherwise needless property.
Ideally I’d like a solution which doesn’t affect my component state, and doesn’t result in repeated code.
Creating an interface component for ReactCSSTransitionGroup
To achieve this, I created an interface component for the ReactCSSTransitionGroup component. It’s usage is identical to the ReactCSSTransitionGroup, however it has an additional prop called transitionAppear
which takes a boolean value. This prop will handle the conditional rendering based on mounted state, then simply wraps the elements you want to animate with the regular ReactCSSTransitionGroup add-on component.
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var ReactCSSTransitionGroupAppear = React.createClass({
propTypes: {
transitionName: React.PropTypes.string.isRequired,
transitionEnter: React.PropTypes.bool,
transitionLeave: React.PropTypes.bool,
transitionAppear: React.PropTypes.bool
},
getInitialState: function() {
return {mounted: false}
},
getDefaultProps: function() {
return {
transitionEnter: true,
transitionLeave: true,
transitionAppear: true
};
},
componentDidMount: function() {
this.setState({ mounted: true });
},
render: function (){
var children;
if(!this.props.transitionAppear){
children = this.props.children;
}
else{
if(this.state.mounted){
children = this.props.children;
}
}
return(
<ReactCSSTransitionGroup
transitionName={this.props.transitionName}
transitionEnter={this.props.transitionEnter}
transitionLeave={this.props.transitionLeave}
>
{children}
</ReactCSSTransitionGroup>
)
}
})
To use this component, I simply include it in my app with the rest of my components and use it in place of the ReactCSSTransitionGroup add-on. Here is a codePen using it in place of the ReactCSSTransitionGroup from the example in the React.js documentation.
Conclusion
While this still obviously isn’t an ideal solution, I think it does provide a cleaner implementation of the most common work-around.
It seems like the ability to apply a transition when the component first appears should be available in the ReactCSSTransitionGroup component soon. There is a pull request on Facebook’s React.js GitHub repo which implements the functionality and it was merged in to the master branch last November. It has yet to make it in to a stable release, but hopefully it’s not too far away. In the meantime, if you’d like to use the wrapper component you can find it on my GitHub page.
Please leave a comment if you have any questions, feedback or suggestions!
Nice article! I had this problem with ReactCSSTransitionGroup and I thought it was an issue with my component. Thanks for clarifying this and presenting an elegant solution!
Nice solution, my only question would be why not just use the normal ReactCSSTransitionGroup component if you don’t need the components to transition on initial render? That way you could remove the transitionAppear prop & subsequent logic from the ReactCSSTransitionGroupAppear component and have it always run the transitions on initial render.
That’s actually a really good point, it could certainly be refactored and used as you’ve described. Thanks for your feedback!
Thanks for the help John.
In your first code example I think you meant getInitialState and not setInitialState 🙂
Whoops, nice catch – thanks 🙂
The solution is too complex, I do following
componentWillMount sets the initialRender flag
componentDidMount resets the initialRender flag
render has the following code
var items = this.initialRender ? [] : this.props.realData;
After the initial render is done, force re-ender by calling
setTimeout( function() {self.forceUpdate();}, 0) from componentDidMount
The idea is following
1) First render renders empty item list
2) The second render is scheduled immediatly after componentDidMount
Hi Anton, thanks for your comment.
Your solution is similar to the one outlined in the Stack Overflow article I linked to – which as I mentioned in the article is a perfectly acceptable solution – but the goal for the solution I proposed is to have a component you can use identically to the regular CSSTransitionGroup without repeating code each time you need to perform a transition on initial render.
Hey John, thank you for your great blogpost!
Since version 0.13 ReactCSSTransitionGroup supports the prop transitionAppear.
TransitionAppear expects a boolean and set true will add the CSS classes example-appear and example-appear-active during the initial mount phase.
I just created a pull request with updates for the documentation. https://github.com/facebook/react/pull/3837
Thanks for sharing this post I was having this problem with ReactCSS but your post helped me a lot.
Codepen not working anymore
https://github.com/html-monster/ReactUpdateAnimation
https://html-monster.github.io/ – Demo
I’ve modify extension of ReactCSSTransitionGroup component. It expands component capabilities, now the class will be added not only while the appearance or removal of an element, but also when an item is updated