Skip to content

react transition group

Created: 2018-03-30 11:15:53 -0700 Modified: 2018-03-30 14:29:21 -0700

This talks about a specific case that I had in Bot Land where I wanted to animate the width on a mission’s progress bar and then do something only when that was complete.

At first, I thought to use CSS transitions, but that could be cumbersome for several reasons:

  • Differentiating between various transitions that happen
  • Having to add/remove event handlers myself

If you do want to use the CSS way of handling animations, I’ve got some code here that I am going to delete from my codebase since I use React now:

* Adds a transitionend handler in a browser-agnostic way to a DOM element.
* @param {DOMElement} element
export function addTransitionEndToElement(element, handler) {
const transitionEndEventName = getTransitionEndEventName();
if (_.isString(transitionEndEventName)) {
element.addEventListener(transitionEndEventName, handler, false);
} else {
setTimeout(handler, 500);
export function removeTransitionEndFromElement(element, handler) {
const transitionEndEventName = getTransitionEndEventName();
if (_.isString(transitionEndEventName)) {
element.removeEventListener(transitionEndEventName, handler, false);
* @return {?string} the name of the transitionend event on this browser
function getTransitionEndEventName () {
var i,
el = document.createElement('div'),
transitions = {
'OTransition':'otransitionend', // oTransitionEnd in very old Opera
for (i in transitions) {
if (transitions.hasOwnProperty(i) &&[i] !== undefined) {
return transitions[i];
return null;

Here’s the solution I came up with in React:

import React, { Component } from 'react'; // eslint-disable-line no-unused-vars
import {Row} from 'jsxstyle';
import onlyUpdateForKeys from 'recompose/onlyUpdateForKeys';
import Transition from 'react-transition-group/Transition';
const _ = require('lodash');
const classNames = require('classnames');
const styles = require('../../../stylesheets/main.less');
class ProgressBar extends Component {
constructor(props) {
this.state = {
* This is simply needed to re-render the react-transition-group
* component; it NEEDS to change from false to true if the animation
* is going to take place.
* @type {boolean}
transitionInProp: false,
renderBackground(state) {
// When animationDuration is specified, it means that we don't want to
// rely on a CSS class passed in for "transition" values and that we'll
// add our own here. This boolean has a big enough impact where I should
// ideally split this class into two classes: one that uses react-
// transition-group for animation, and one that doesn't.
const isAnimatingViaReactTransitionGroup = !_.isNil(this.props.animationDuration);
const fillPercent = _.round(_.clamp(this.props.fillPercent, 0, 1), 2) * 100.0;
const endingFillPercent = _.round(_.clamp(_.defaultTo(this.props.endingFillPercent, this.props.fillPercent), 0, 1), 2) * 100.0;
const defaultStyle = {
width: `${fillPercent}%`,
if (isAnimatingViaReactTransitionGroup) {
defaultStyle.transition = `width ${this.props.animationDuration}ms ease-in`;
const transitionStyles = {
entering: { width: `${endingFillPercent}%` },
entered: { width: `${endingFillPercent}%` },
const styleToUse = {
const finalStyle = isAnimatingViaReactTransitionGroup
? styleToUse
: defaultStyle;
return (
<span style={finalStyle} className={this.props.backgroundStyle}/>
componentDidMount() {
setTimeout(() => {
transitionInProp: true,
}, 500);
onFinishAnimatingProgressBar() {
if (_.isFunction(this.props.onFinishAnimatingProgressBar)) {
render() {
const foregroundCss = classNames(
return (
<Row className={this.props.containerStyle}>
<Row alignItems='center' className={foregroundCss}>
const enhance = onlyUpdateForKeys([
* The amount of ms it takes to animate CSS properties.
* @type {number}
export default enhance(ProgressBar);

Things to note:

  • I need to have the starting and ending values at the same time, otherwise I can’t apply the animation correctly since it needs to be done through JS (since the mission progress is dynamic).
    • Note: if you always wanted to animate between static values, then you could still use this solution and just manually type “0%” and “100%” or something like that.
  • I have a setTimeout on the “transitionInProp” so that the animations don’t start until the component has been mounted for at least 500 ms. I had tried instead to just change “endingFillPercent” after the component had already mounted, but that turned out to be a terrible idea because a CSS transition would have already kicked off (internally in react-transition-group) as soon as “transitionInProp” is true, so the “onEntered” callback would get hit way before the progress bar finished filling up if you changed the prop mid-animation.