DEV Community

Cover image for How to Animate Components' Entrance and Exit in React
Shahed Nasser
Shahed Nasser

Posted on • Originally published at blog.shahednasser.com on

How to Animate Components' Entrance and Exit in React

This article was originally published on my personal blog

Say you have a list component in React where the user can add or remove items in the list. It would be nice to animate the items as they are being added or removed from the list.

In this tutorial, we'll cover how to animate components' entrance and exit in React using React Transition Group.

You can find the full code for this tutorial in this GitHub Repository, and you can see a working demo.

What is React Transition Group

React Transition Group is a library that allows you to add animation on a component or multiple components' entrance and exit.

React Transition Group does NOT do the animation for you, that is it does not provide the animation. It facilitates adding the animation either through CSS classes or styles when a component enters or exits.

React Transition Group exposes the components that will allow you to easily do that. There are 4 components that it exposes: Transition, CSSTransition, SwitchTransition, and TransitionGroup.

We'll go over different use cases when it comes to animating elements, and in each use case which component you should use and how you can use them.

Animating a Single Element

The first use case we'll look at is animating a single element. Let's say we have an element that we want to animate every time it enters or exits.

There are 2 components we can use: Transition and CSSTransition. The recommended component is CSSTransition, but we'll cover both.

Using Transition

With the Transition component, you can add CSS styling based on the different states. This component covers the states:

  1. entering: Before the element enters.
  2. entered: Element has entered.
  3. exiting: Before the element exits
  4. exited: The element has exited.

Generally, CSSTransition is recommended to be used instead of Transition. Transition is provided as a platform-agnostic base component.

For this example, we'll have a button that will allow us to show or hide a picture of a cat. First, we need to create a state variable to store and indicate whether the image should be shown or not.

const [transitionState, setTransitionState] = useState(false)
Enter fullscreen mode Exit fullscreen mode

Then, We'll use the Transition component to wrap the img element. The Transition component takes the prop in which is a boolean variable that indicates whether the component should enter or not. We should pass the state variable to this prop.

Another required prop that Transition accepts is timeout which defines the duration of the animation.

<Transition in={transitionState} timeout={300} >
...
</Transition
Enter fullscreen mode Exit fullscreen mode

Inside Transition, a function is passed which receives the state parameter. This parameter indicates the current state of the component, which will be one of the 4 states mentioned earlier.

Using that state variable we can change the CSS styling of the component to animate it.

So, we need to create an object that holds the stylings we want to apply:

const transitions = {
  entering: {
    display: 'block'
  },
  entered: {
    opacity: 1,
    display: 'block'
  },
  exiting: {
    opacity: 0,
    display: 'block'
  },
  exited: {
    opacity: '0',
    display: 'none'
  }
};
Enter fullscreen mode Exit fullscreen mode

Notice how we set the object keys or properties as the name of the states.

Then, in the child function of Transition, we set the style based on the current state:

<Transition in={transitionState} timeout={300} >
    {state => (
        <img src="https://cataas.com/cat" alt="Cat" style={{
            transition: 'all .1s',
            opacity: 0,
            display: 'none',
            ...transitions[state]
            }} className="mt-2" />
    )}
</Transition>
Enter fullscreen mode Exit fullscreen mode

Notice how the function returns the img element. Inside the style prop of the img element we first set the default styling, then we add the styling based on the state using this line:

...transitions[state]
Enter fullscreen mode Exit fullscreen mode

Now, everytime the state changes when the component enters or exits, the state variable in the child function will change. So, the styling of the element will change based on the value of the state variable, which will add animation to the element.

Also, the image we're using is from Cat as a service.

The only thing left is to add a button to toggle the state variable transitionState to show and hide the image:

<Button onClick={() => setTransitionState(!transitionState)}>{transitionState ? 'Hide' : 'Show'} Cat</Button>
Enter fullscreen mode Exit fullscreen mode

Using CSSTransition

The recommended approach for this use case is using CSSTransition. The CSSTransition component allows you to add classes for each state, which gives you more freedom to add animation to your components.

To make the animation easier we'll use Animate.css which is a CSS animation library that provides us with many animations we can easily use.

To animate an element with CSSTransition, you wrap it within the CSSTransition component. Similar to Transition CSSTransition receives the in prop which indicates whether the component should enter or exit. Also, it accepts the timeout prop which determines the duration of the animation.

Unlike Transition, CSSTransition receives the prop classNames which allows us to define the classes that should be added based on the different states.

classNames can be an object or a string. If a string is passed, the class will be used as a prefix for the different states. For example, if you pass to classNames "fade", the class fade-enter will be added to the component when it enters. When the component exits, the class fade-exit is added. The same goes for the rest of the states.

If an object is passed as the value for classNames, then the keys or properties should be the name of the state, and the value should be the class to apply for that state. For example:

classNames={{
 appear: 'fade-in',
 appearActive: 'fade-in-active',
 appearDone: 'fade-in-appeared',
 enter: 'fade-in-enter',
 enterActive: 'fade-in-enter-active',
 enterDone: 'fade-in-done',
 exit: 'fade-out',
 exitActive: 'fade-out-active',
 exitDone: 'fade-out-active',
}}
Enter fullscreen mode Exit fullscreen mode

Notice that you don't need to add class names for all these states. This just gives you more freedom and flexibility over it. Generally, you should set the class that you want to apply when the element enters to enterActive, and the class that you want to apply when the element exits to exitActive. Basically, the active phase of each state is when you should apply the animation.

So, back to our example, we want to animate an image of a cat as it is toggled by a button. First, we'll add 2 state variables:

const [showCat, setShowCat] = useState(false);
const [imageClasses, setImageClasses] = useState("d-none");
Enter fullscreen mode Exit fullscreen mode

showCat will be used for the in prop to determine when the element should enter and exit. As for imageClasses, we'll get to why we need it later on.

Next, we'll add the CSSTransition component:

<CSSTransition in={showCat} timeout={500} classNames={{
          enterActive: 'animate__bounceIn',
          exitActive: 'animate__bounceOut'
        }} 
        onEnter={showImage}
        onEntered={removeOpacity}
        onExited={hideImage}
        className={`animate__animated my-4 ${imageClasses}`}>
...
</CSSTransition>
Enter fullscreen mode Exit fullscreen mode

Notice the following:

  1. On enterActive, which is when the element should appear, we add the class animate __bounceIn, and on exitActive, which is when the element should exit, we add the class animate__ bounceOut. Both these classes are from the Animate.css library.
  2. We have added a listener for onEnter, which will be triggered when the element enters; a listener for onEntered, which will be triggered when the element has finished entering; a listener for onExited which will be triggered when the element has exited. We'll implement these listeners in a bit.
  3. We have passed a className prop that would add default classes to the child component.

As you can see, we're using the state variable imageClasses inside the string passed to className. When using CSSTransition, you'll assume that the exit state will be applied initially when the initial value passed to in is false. That's actually not true. Initially, if the value of the in prop is false, no classes are added.

As we don't want the image to be initially visible, we're using a state variable to add the Bootstrap class d-none as we're using it in our project. This class will hide the element when added.

And this is why we added the event listeners. We'll change the value of imageClasses based on each state:

function hideImage() {
    setImageClasses("d-none");
}

function showImage(node) {
    setImageClasses("d-block");
    node.style.opacity = 0;
}

function removeOpacity (node) {
    node.style.opacity = 1;
}
Enter fullscreen mode Exit fullscreen mode

Inside CSSTransition we add the element we want to animate:

<CSSTransition in={showCat} timeout={500} classNames={{
          enterActive: 'animate__bounceIn',
          exitActive: 'animate__bounceOut'
        }} 
        onEnter={showImage}
        onEntered={removeOpacity}
        onExited={hideImage}
        className={`animate__animated my-4 ${imageClasses}`}>
    <img src="https://cataas.com/cat" alt="Cat" />
</CSSTransition>
Enter fullscreen mode Exit fullscreen mode

That's it! The only thing left is to add the button to toggle the showCat state variable:

<Button onClick={() => setShowCat(!showCat)}>{showCat ? 'Hide' : 'Show'} Cat</Button>
Enter fullscreen mode Exit fullscreen mode

Now, every time you click the button the classes will change based on the state.

Animate a Group of Element

This applies to the first example mentioned in this article. Let's say you have a list and you want to animate whenever an element is added or removed from it. The elements will generally be dynamic, so you can't use CSSTransition or Transition on them one by one.

Using TransitionGroup

The component TransitionGroup wraps a list of CSSTransition or Transition components and manages their animation based on their states. In a use case where the list of elements to be added is dynamic, it's useful to use this component.

You pass CSSTransition or Transition components as children. There's no need to pass props to TransitionGroup, as the configuration for the animation is done through the props passed to the children components.

In this example, we'll have an array of 4 elements in the beginning. Then, the user can add an item by clicking on a button or removing an item by clicking on the X icon.

How to Animate Components' Entrance and Exit in React

To make the implementation easier, we'll have an array of languages to add items from it randomly:

const defaultLanguages = [
  {
    id: 1,
    name: 'Java'
  },
  {
    id: 2,
    name: 'JavaScript'
  },
  {
    id: 3,
    name: 'PHP'
  },
  {
    id: 4,
    name: 'CSS'
  },
  {
    id: 5,
    name: 'C'
  },
  {
    id: 6,
    name: 'C#'
  },
  {
    id: 7,
    name: 'HTML'
  },
  {
    id: 8,
    name: 'Kotlin'
  },
  {
    id: 9,
    name: 'TypeScript'
  },
  {
    id: 10,
    name: 'Swift'
  }
];
Enter fullscreen mode Exit fullscreen mode

And we'll use a one-liner function from 1Loc to get random elements from an array:

const randomItems = (arr, count) => arr.concat().reduce((p, _, __, arr) => (p[0] < count ? [p[0] + 1, p[1].concat(arr.splice((Math.random() * arr.length) | 0, 1))] : p), [0, []])[1];
Enter fullscreen mode Exit fullscreen mode

Then, we'll define a state variable which will be the array of languages we'll show the user in a list:

const [languages, setLanguages] = useState(randomItems(defaultLanguages, 4));
const [counter, setCounter] = useState(11);
Enter fullscreen mode Exit fullscreen mode

We also define a state variable counter which we'll use to change the id property from the defaultLanguages array when adding a new item to the languages array. This is just to ensure that the IDs are unique when we are choosing random items from the array.

Then, we render a TransitionGroup component and inside it we loop over the languages state variable and render a CSSTransition component for that variable:

<TransitionGroup>
    {languages.map(({id, name}) => (
        <CSSTransition key={id} classNames={{
                enterActive: 'animate __animated animate__ lightSpeedInLeft',
                exitActive: 'animate __animated animate__ lightSpeedOutLeft'
              }} timeout={900}>
            <li className="p-3 border mb-3 shadow-sm rounded border-info d-flex justify-content-between">
                <span>{name}</span>
                <CloseButton onClick={() => removeLanguage(id)}></CloseButton>
             </li>
            </CSSTransition>
    ))}
</TransitionGroup>
Enter fullscreen mode Exit fullscreen mode

Notice that we're passing the class animate __animated animate__ lightSpeedInLeft for the state enterActive. As mentioned in the previous section, this class we'll be added when the element enters. We're also passing the class animate __animated animate__ lightSpeedOutLeft for the state exitActive. As mentioned in the previous section, this class we'll be added when the element exits. Also we're passing the timeout prop with value 900.

Inside CSSTransition we pass the element we want to animate which is an li element. The element shows the name of the language and has a CloseButton component which on click should remove the language from the list. Please note that the CloseButton comes from the React Bootstrap which we're using just for styling purposes.

As you can see TransitionGroup is only used as a wrapper to these elements.

We also need to add a button to add languages:

<Button onClick={addLanguage}>Add</Button>
Enter fullscreen mode Exit fullscreen mode

What's left is to implement the event listeners addLanguage and removeLanguage:

function addLanguage() {
    const newLanguages = languages.splice(0);
    const newItem = Object.assign({}, randomItems(defaultLanguages, 1)[0]);
    newItem.id = counter;
    newLanguages.push(newItem);
    setLanguages(newLanguages);
    setCounter(counter + 1);
}

function removeLanguage (id) {
    const newLanguages = languages.splice(0);
    const ind = newLanguages.findIndex((language) => language.id === id);
    if (ind !== -1) {
        newLanguages.splice(ind, 1);
        setLanguages(newLanguages);
    }
}
Enter fullscreen mode Exit fullscreen mode

The addLanguage listener picks a random item from the array. We use Object.assign to clone the item from the array instead of getting the item by reference. We then change the id to make sure it's unique.

In the removeLanguage listener we just find the index of the language in the array and remove it.

That's all! If you try it out, items that are added by clicking on the "Add" button will be animated as they enter. Items will also be animated when they exit by clicking on the X icon.

Applying Animation With a Switch

The last case we'll cover is animating something based on its change of state. Let's say we have a button that would toggle between two states, and these two states would require a change in the appearance of another element. For this case, we can use the SwitchTransition component.

The SwitchTransition wraps a CSSTransition or Transition element. It accepts one prop mode which can be of two values: out-in or in-out, with out-in being the default. When choosing out-in, it means that the old state exits first then the new state enters. When choosing in-out it's the opposite; the new state enters then the old state exits.

When the state of the component changes, the component exits and a new component with the new state enters.

In this example, we'll have an Alert, which is a component exposed by React Bootstrap. We'll have a state that will toggle the variant, which is the background color and the theme of the Alert component, between danger and success. We'll also change the text of the Alert component based on the variant.

First, we'll define the state variable to toggle the state of the Alert component:

const [isDanger, setIsDanger] = useState(true);
Enter fullscreen mode Exit fullscreen mode

Then, we'll render the SwitchTransition component which will take as a child a CSSTransition component to manage the animation of the Alert component:

<SwitchTransition mode="out-in">
    <CSSTransition key={isDanger} classNames={{
        enterActive: 'animate __animated animate__ flipInX',
        exitActive: 'animate __animated animate__ flipOutX'
    }}
    timeout={500}>
        <Alert variant={isDanger ? 'danger' : 'success'}>{isDanger ? "You're in danger" : "Danger cleared"}</Alert>
    </CSSTransition>
</SwitchTransition>
Enter fullscreen mode Exit fullscreen mode

As you can see we pass to SwitchTransition the mode out-in, but this is the default mode so it's optional to pass.

For CSSTransition we pass it the prop key which will be used to enter and exit elements based on the state. When the state variable isDanger changes, the component will be removed and a new one with the new value will be added. This key prop behaves exactly as it would when you render items from an array using map.

For the enterActive animation state, we add the class animate __animated animate__ flipInX. For the exitActive animation sate, we add the class animate __animated animate__ flipOutX.

As for the child of CSSTransition we pass the Alert component, which sets the variant and text based on the value of isDanger.

Finally, we'll render a button to toggle the value of isDanger:

<Button onClick={() => setIsDanger(!isDanger)}>
    {isDanger ? 'Clear Danger' : 'Bring Danger'}
</Button>
Enter fullscreen mode Exit fullscreen mode

If you try it now, you'll see that when you click the button the Alert will exit and the new one will enter. This is because of the mode out-in.

If you try to change the mode to in-out, you'll see that when you click the button a new Alert will enter and then the old one will exit.

Conclusion

Adding animation to components provides a nice user experience and add a flair to your website.

In this tutorial, we learned how to use React Transition Group to animate a component's enterance or exit. Remember, this library does not add the animation for you. This library exposes components that will allow you to add the animation yourself.

Discussion (0)