TypeOfNaN

How to Animate Items Out of an Array in React

Nick Scialli
March 28, 2021

Animating items out of an array in React can be trickier than you’d think. If you have tried to roll your own solution yet, you have likely been frustrated by the fact that, once your state has been updated with an item removed, it’s gone (and hard to hold on to for the sake of animation!).

A Couple Approaches

There are a couple approaches to this problem that I’ll discuss.

  1. Rolling your own solution (very much not recommended)
  2. Using react-transition-group (use this!)

Clearly, I favor using the established package over rolling your own solution. I have rolled my own before and it was complicated and buggy. Still—after I discuss the preferred solution I’ll do a quick write-up on how I rolled my own.

A Simple Todo List Example

Let’s get into a simple example—a todo list. Here’s the code for a todo list that remove items from the list without any animation:

import { useState } from 'react';

const initial = ['Walk dog', 'Sweep floors', 'Do homework'];

export default function App() {
  const [todos, setTodos] = useState(initial);

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>
          {todo}{' '}
          <button
            onClick={() => {
              setTodos(todos.filter((t) => t !== todo));
            }}
          >
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

If we test drive the app, we see the following. Again, items being removed from the list without any animation.

removing todo list items

Using react-transition-group

react-transition-group offers some super helpful components that can help with animations. To use this package, we first have to install it.

If you’re using npm, you can run

npm i react-transition-group

Or if you’re yarn, run

yarn add react-transition-group

The first thing we’ll want to do to animate removing items from the list is to wrap the items we’re animating in a TransitionGroup component. The TransitionGroup component takes its own component prop that will allow us to specify what type of DOM element (or custom component) should be used. In our cases, the transition group is inside an unordered list, so we can use the string "ul".

import { useState } from 'react';
import { TransitionGroup } from 'react-transition-group';

const initial = ['Walk dog', 'Sweep floors', 'Do homework'];

export default function App() {
  const [todos, setTodos] = useState(initial);

  return (
    <TransitionGroup component="ul">
      {todos.map((todo) => (
        <li key={todo}>
          {todo}{' '}
          <button
            onClick={() => {
              setTodos(todos.filter((t) => t !== todo));
            }}
          >
            Delete
          </button>
        </li>
      ))}
    </TransitionGroup>
  );
}

Next up, we have to actually tell each item how to animate. To do so, we’ll use another component: CSSTransition. This will be inside our todos map and will take a few arguments:

  • a key (since it’s the outer-most component inside a list)
  • a timeout, which specifies the transition time
  • a classNames prop, which will be a class we can use to style our transition
import { useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

const initial = ['Walk dog', 'Sweep floors', 'Do homework'];

export default function App() {
  const [todos, setTodos] = useState(initial);

  return (
    <TransitionGroup component="ul">
      {todos.map((todo) => (
        <CSSTransition key={todo} timeout={700} classNames="item">
          <li>
            {todo}{' '}
            <button
              onClick={() => {
                setTodos(todos.filter((t) => t !== todo));
              }}
            >
              Delete
            </button>
          </li>
        </CSSTransition>
      ))}
    </TransitionGroup>
  );
}

Looking good! One last piece: we need to actually have styles associated with our item. By convention, react-transition-group will use the following styles:

.[classname]-enter {
}
.[classname]-enter-active {
}
.[classname]-exit {
}
.[classname]-exit-active {
}

Since we’re currently only worried about animating the exit of an item from our list, we can actually get by with only using the .item-exit-active class:

style.css

.item-exit-active {
  opacity: 0;
  transition: opacity 700ms ease-out;
}

Here, we’re saying that, when the transition is active, set the opacity to 0 but with a 700ms ease-out transition.

We can incorporate this into our app by just including the style now.

import { useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './styles.css';

const initial = ['Walk dog', 'Sweep floors', 'Do homework'];

export default function App() {
  const [todos, setTodos] = useState(initial);

  return (
    <TransitionGroup component="ul">
      {todos.map((todo) => (
        <CSSTransition key={todo} timeout={700} classNames="item">
          <li>
            {todo}{' '}
            <button
              onClick={() => {
                setTodos(todos.filter((t) => t !== todo));
              }}
            >
              Delete
            </button>
          </li>
        </CSSTransition>
      ))}
    </TransitionGroup>
  );
}

And if we run our app, we can see the transition working swimmingly:

removing items with animation

Rolling Your Own Solution (Not Recommended!)

For completeness, I figured I might as well discuss how you could go about doing this yourself—although I don’t recommend it at all.

First, you’d need to maintain a cache of items. This is because you’d need some way to hold on to an item long enough to animate it. You’d want to detect when a item is removed so that you could start the animation process. Finally, you’d want a timer to actually remove the item after animation is complete.

You may end up mixing CSS transition and JavaScript. You’d have to figure out how to handle multiple items being removed at the same time and make sure their timeouts work independently. Finally, you’d need to make sure to cancel any timeouts still going if the component unmounts or else you’ll have a memory leak.

Doesn’t sound fun, does it? I promise, it isn’t!

Conclusion

Animation with React, especially when removing items from state, can appear complex. Fortunately, we have packages like react-transition-group that can help us get the job done in short order!

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli