React Animations with React Transition Group

We create open-source because we love it, and we share our finding so everyone else can benefit as well.

React Animations with React Transition Group

When the time comes to add the polish to your apps, animations cannot often be overlooked. Even simple animations make a world of difference in your app’s presentation. We will take a look at how to quickly add transition animations using react-transition-group.

Understanding Animations in React

It’s not hard to see that animations are handled differently in React. The way react-transition-group handles this, is with a simple transitional state. Rather the animation is entering a transition, or exiting a transition. When our state changes, we see an animated transition.

We will look at some altered versions of the official RTG examples to demonstrate how to use this library to create animations. All code in this article can be found in the following Code Sandbox.

Animations in React Transition Group

React Transition group handles simple transitional animations, hence the name. Any animation that has a starting and stopping point, you can create with this module. The animation you want to create determines which class module to use. Let’s take a look at each, and what they work best for.

Transtion Animations

The Transition module is the simplest module within react-transition-group. It takes in the criteria for the starting point, and stopping point. It does this for the entrance transition, and the exit transition. The idle position of the component is your starting point, where the final destination of the animation is the stopping point.

Let’s demonstrate this with a fade example, where we animate opacity. Our starting point will be an opacity of 1, so we want to transition to opacity of 0, when our state changes. We start creating our animation by setting up our CSS transition into a defaultStyle object, then creating a transitionStyle object with our opacity values. The transition component has an entered, entering, exited, and exiting prop so we can tell the component what to do at each end of the transition animation. In our fade, we use the entered, and exited prop value, setting the entered to “opacity: 1” (opaque), and exited to “opacity: 0” (transparent).

With the CSS portion taken care of, we can now wrap the components we want to animation with our Transition component. We then spread the CSS styles objects into the style prop of the children that we want to fade. All that’s left is to add a state switch, and set the Transition component to animate based on that state.

import React, { useState } from "react";
import { Alert, Button } from "react-bootstrap";
import { Transition } from "react-transition-group";

const duration = 300;

const defaultStyle = {
  transition: `opacity ${duration}ms ease-in-out`,
  opacity: 0
};

const transitionStyles = {
  entered: { opacity: 1 },
  exited: { opacity: 0 }
};

export default function Pulse() {
  const [inProp, setInProp] = useState(false);

  return (
    <Transition in={inProp} timeout={duration}>
      {(state) => (
        <>
          <div
            style={{
              ...defaultStyle,
              ...transitionStyles[state]
            }}
          >
            <Alert variant="primary">
              {inProp ? "Hello!" : "I'm fading!"}
            </Alert>
          </div>
          <Button onClick={() => setInProp(!inProp)}>
            Click to Fade {inProp ? "out" : "in"}
          </Button>
        </>
      )}
    </Transition>
  );
}

As you can see, this is a very simple implementation, but even this slight addition can make a world of difference.

Using CSS Transistions

Transitions are simple, so when you want to create a specific animation you can use the CSSTransition component. This component gives you the ability to set the animation based on CSS properties. Similar to our Transition animation, we our animation is based on styling we set. Instead of using a styling object, we set a style prefix, and write our styles in CSS.

Here is our CSSTransition animation in React:

import React, { useState } from "react";
import { Alert, Button, Container } from "react-bootstrap";
import { CSSTransition } from "react-transition-group";

import "./styles.css";

export default function CSSAlert() {
  const [showButton, setShowButton] = useState(true);
  const [showMessage, setShowMessage] = useState(false);
  return (
    <Container style={{ paddingTop: "2rem" }}>
      {showButton && (
        <Button onClick={() => setShowMessage(true)} size="lg">
          Show Message
        </Button>
      )}
      <CSSTransition
        in={showMessage}
        timeout={300}
        classNames="alert"
        unmountOnExit
        onEnter={() => setShowButton(false)}
        onExited={() => setShowButton(true)}
      >
        <Alert
          variant="primary"
          dismissible
          onClose={() => setShowMessage(false)}
        >
          <Alert.Heading>Animated alert message</Alert.Heading>
          <p>This alert message is being transitioned in and out of the DOM.</p>
          <Button onClick={() => setShowMessage(false)}>Close</Button>
        </Alert>
      </CSSTransition>
    </Container>
  );
}

The “classNames” prop used in our CSSTransition component set our class name prefix. In the alertStyles css file, we find our transition classes.

.alert-enter {
  opacity: 0;
  transform: scale(0.9);
}
.alert-enter-active {
  opacity: 1;
  transform: translateX(0);
  transition: opacity 300ms, transform 300ms;
}
.alert-exit {
  opacity: 1;
}
.alert-exit-active {
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 300ms, transform 300ms;
}

Just like our Transition’s enter and entered states, CSSTransition uses the Enter and Enter-Active classes for the entrance animation. The Exit and Exit-Active classes are for our reverse animation. While this works similarly to Transition, Unlike the Transition, we have much more control over our animations.

Using Switch Transitions

The Switch Transition is useful when you want to control the transitions between renders. This is easier demonstrated in the example. The component has two different modes to determine its behavior: The first, out-in which waits for the old child to transition before transitioning the new child. The second, in-out mode starts the transition for the new child first, and then the transition for the old child.

react transition group switch transition component example
Switching Between Transition Modes with Switch Transition

In our example we create this same example, using the Switch Transition, and a CSSTransition component.

import React from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import { Button, Form } from "react-bootstrap";
import "./styles.css";
import Title from "../Title";

const modes = ["out-in", "in-out"];

export default function Switch() {
  const [mode, setMode] = React.useState("out-in");
  const [state, setState] = React.useState(true);
  return (
    <>
      <Title title="React Transition Group SwitchTransition" />
      <div className="label">Mode:</div>
      <div className="modes">
        {modes.map((m) => (
          <Form.Check
            key={m}
            custom
            inline
            label={m}
            id={`mode=msContentScript${m}`}
            type="radio"
            name="mode"
            checked={mode === m}
            value={m}
            onChange={(event) => {
              setMode(event.target.value);
            }}
          />
        ))}
      </div>
      <div className="main">
        <SwitchTransition mode={mode}>
          <CSSTransition
            key={state}
            addEndListener={(node, done) => {
              node.addEventListener("transitionend", done, false);
            }}
            classNames="fade"
          >
            <div className="button-container">
              <Button onClick={() => setState((state) => !state)}>
                {state ? "Hello, world!" : "Goodbye, world!"}
              </Button>
            </div>
          </CSSTransition>
        </SwitchTransition>
      </div>
    </>
  );
}

The CSS Transition component is treated the same, the only difference is that we have a wrapping component telling the component how to transition the children. Here’s our CSS:

.fade-enter .btn {
  opacity: 0;
  transform: translateX(-100%);
}
.fade-enter-active .btn {
  opacity: 1;
  transform: translateX(0%);
}
.fade-exit .btn {
  opacity: 1;
  transform: translateX(0%);
}
.fade-exit-active .btn {
  opacity: 0;
  transform: translateX(100%);
}
.fade-enter-active .btn,
.fade-exit-active .btn {
  transition: opacity 500ms, transform 500ms;
}

You may have noticed a new prop on the CSSTransition, the addEndListener. This optional prop is available on any transition component. It allows you to pass a callback to be used once the transition finishes. In this particular example, we use it to tell the component when to transition the opposing child component.

Multiple Animations with Transition Group

Last on our list is the Transition Group, and the name alone should give an idea what it does. The component is used when you want to apply animations to a group of children.

react transition group todo item transition animation
Transition Group Todo List

In the following example, we use a simple Todo List, adding a group transition to each todo list child component.

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import {
  Container,
  ListGroup,
  Button,
} from 'react-bootstrap';
import {
  CSSTransition,
  TransitionGroup,
} from 'react-transition-group';
import uuid from 'uuid';

import './styles.css';

function TodoList() {
  const [items, setItems] = useState([
    { id: uuid(), text: 'Buy eggs' },
    { id: uuid(), text: 'Pay bills' },
    { id: uuid(), text: 'Invite friends over' },
    { id: uuid(), text: 'Fix the TV' },
  ]);
  return (
    <Container style={{ marginTop: '2rem' }}>
      <ListGroup style={{ marginBottom: '1rem' }}>
        <TransitionGroup className="todo-list">
          {items.map(({ id, text }) => (
            <CSSTransition
              key={id}
              timeout={500}
              classNames="item"
            >
              <ListGroup.Item>
                <Button
                  className="remove-btn"
                  variant="danger"
                  size="sm"
                  onClick={() =>
                    setItems(items =>
                      items.filter(item => item.id !== id)
                    )
                  }
                >
                  &times;
                </Button>
                {text}
              </ListGroup.Item>
            </CSSTransition>
          ))}
        </TransitionGroup>
      </ListGroup>
      <Button
        onClick={() => {
          const text = prompt('Enter some text');
          if (text) {
            setItems(items => [
              ...items,
              { id: uuid(), text },
            ]);
          }
        }}
      >
        Add Item
      </Button>
    </Container>
  );
}

The setup is the same as our Transition, except that we set an iterable key, wrapping the children in a TransitionGroup component. Our animation is handled by the child CSSTransition, and just like a single component, the CSS is applied the same way, except that it’s applied to every child. Here’s our CSS:

.todo-list {
  padding: 20px;
}

.item-enter {
  opacity: 0;
  left: 100%;
}

.item-enter-active {
  opacity: 1;
  left: 0%;
  transition: all 500ms ease-in;
}
.item-exit {
  opacity: 1;
  right: 0%;
}
.item-exit-active {
  right: -100%;
  transition: all 500ms ease-in;
}

As for the Group component, we only apply an exit prop to enable our exit transitions. Without it, we would lose the animation when removing an item from our list.

Summary

Those are the basics of the React Transition Group library, and hopefully you found React animations to be much less intimidating. These are all simple animations, but there are many more articles to come covering more advanced animations.

 

No Comments

Add your comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.