react animations
Sep 23, 2016 08:15 · 921 words · 5 minute read
Previously, I wrote about React animations for creating a dropdown menu, but after spending more and more time working with them, I decided to explore exactly how animations fit into the React lifecycle.
Two Animations
There are two kinds of component animations in React.
- The component is already in the DOM
- The component is entering the DOM
The first kind are not that hard to deal with. In the past, I’ve added a CSS class to trigger a transition. There are also React specific libraries like radium.
The second kind of component requires a bit more work.
To understand why, consider the React lifecycle.
React Lifecycle
In React, you create a series of components that become part of a the virtual DOM:
"PotatoHead": {
"head": {
"peg": null
},
"body": {
"topPeg": "eyes",
"middlePeg": null,
"bottomPeg": null
},
"bottom": {
"peg": "shoes"
}
}
The virtual DOM renders into the DOM as seen in the browser:

Eventually something happens in the app and that triggers an action:
dispatch(existentialCrisis());
This changes the virtual DOM:
"PotatoHead": {
"head": {
"peg": null
},
"body": {
"topPeg": "eyes",
"middlePeg": null,
"bottomPeg": "mouth"
},
"bottom": {
"peg": "shoes"
}
}
Which updates the actual DOM:

The thing that makes React animations difficult is that there is no point in the lifecycle that they fit in.
Think about the lifecycle hooks:
Where are animations supposed to fit in?
You can’t use the componentWillMount
hook because there is no DOM element to animate.

And you can’t use the componentDidMount
hook because the element is already there, so you would have to rerender it (causing a potential loop)
and you may see a jump as the component gets an orientation change after being added to the DOM.

Solution: More Lifecycle Hooks
The React team recognized this problem and created a higher level component called ReactTransitionGroups that can wrap additional components giving them more lifecycle hooks.
Any component that is wrapped within a ReactTransitionGroup component will get a couple new lifecycle hooks.
One of the most relevant is componentWillEnter
which will be fired as soon as the component is mounted (the same time as componentDidMount).
componentWillEnter
creates a lifecycle hook that we can use to animate an components we want.
All other animations will be blocked until a callback is called.
Chang Wang has an excellent example of how to build an animation with Tween using ReactTransitionGroups.
If you need fine grained control or want to use a specific library, than ReactTransitionGroups
are the way to go.
If you need something even easier, React made a further abstraction called ReactCSSTransitionGroups
that utilize transition groups but allow the developer to use CSS transitions to handle any animations.
ReactCSSTransitionGroups
ReactCSSTransitionGroups
work by wrapping components and then adding specific classes to child components for a designated amount of time.
Here’s an example of how you would set it up:
const ShapeContainer = ({elements}) => (
<div id = "shapes">
<ReactCSSTransitionGroup
transitionName = "shape"
transitionEnterTimeout={2000}
transitionLeaveTimeout={2000}
>
{elements}
</ReactCSSTransitionGroup>
</div>
)
Notice a few things. I gave the transition name of shape and a transition enter timeout of 2000 milliseconds and the same for the transition leave.
This means that a few classes with the base name shape will be added to every child component for 2 seconds before they are automatically removed.
To take advantage of any transitions, we need to define them with CSS:
.shape-enter {
transform: scale(0);
}
.shape-enter.shape-enter-active {
transform: scale(1);
transition: all 2s ease-in;
}
Rendered code will look like this.
The span
tag is the ReactCSSTransitionGroup
(although you can specify other tags like div
or ul
).
<div id="shapes">
<span data-reactid="0.1">
</span>
</div>
Any child componenent that is added will receive that shape-enter
class.
This sets up the initial styling that will be animated (in this example it is effectively hidden).
<div id="shapes">
<span data-reactid="0.1">
<svg data-reactid=".0.2"
class="shape-enter">
<circle data-reactid=".0.2"></circle>
</svg>
</span>
</div>
In the next tick, the component will receive the shape-enter-active
class which will trigger the CSS transition.
In this example, it will scale it up to full size.
The timing for the transition should match the timeout on CSS transition group.
<div id="shapes">
<span data-reactid="0.1">
<svg data-reactid=".0.2"
class="shape-enter shape-enter-active">
<circle data-reactid=".0.2"></circle>
</svg>
</span>
</div>
After the timeout is reached, the classes are removed from the component.
<div id="shapes">
<span data-reactid="0.1">
<svg data-reactid=".0.2"
class="">
<circle data-reactid=".0.2"></circle>
</svg>
</span>
</div>
Any subsequent children will go through the same process.
Animating a component leaving is even more important.
Without transition groups, a component will disappear before anything can happen to it.
However, with ReactCSSTransitionGroups
the whole process happens in reverse.
Let’s start with the leaving css. We’ll start at full size and shrink to nothing.
.shape-leave {
transform: scale(1);
}
.shape-leave.shape-leave-active {
transform: scale(0);
transition: all 2s ease-in;
}
A component in the DOM will first receive the shape-leave
class:
<div id="shapes">
<span data-reactid="0.1">
<svg data-reactid=".0.2"
class="shape-leave">
<circle data-reactid=".0.2"></circle>
</svg>
</span>
</div>
After that, it will receive the shape-leave-active
class:
<div id="shapes">
<span data-reactid="0.1">
<svg data-reactid=".0.2"
class="shape-leave shape-leave-active">
<circle data-reactid=".0.2"></circle>
</svg>
</span>
</div>
And when the timeout set on the ReactCSSTransitionGroup
component is reached, the element is removed.
Note: The element is not removed after the animation is complete, but when the timeout is reached.
So if the CSS transition is longer than the timeout it will just disappear.
<div id="shapes">
<span data-reactid="0.1">
</span>
</div>
And that’s all it takes.
Here’s a full demo you can try:
See the Pen ReactCSSTransitionGroup by Joe Morgan (@jsmapr1) on CodePen.