Animating Lists in React: features comparison and opinion

I think rendering lists of data it's a very common pattern for every application and therefore we as front end engineers have to deal with the most efficient way of displaying these lists, depending on the number of items, the kind of data we are displaying (images, numbers, text, inputs, etc.) and how the user will interact with these lists, by sorting them, deleting items, adding items, and all of those ~~fun~~ CRUD operations we're all familiar with.
![annie-spratt-AE1XpXLxXSA-unsplash.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/6e8813d2-593b-219b-46d9-8ca9a55c116d.jpeg)

Let's begin by defining the tech stack for this comparison:
- **Core environment**
   -  [`React 17`](https://reactjs.org/blog/2020/10/20/react-v17.html) - using create-react-app + typescript -
   - [`@stitches/react`](https://stitches.dev/) - CSS-in-JS library with SSR (just for styling)
   - [`react-use-hooks`](https://github.com/streamich/react-use) - Just hooks for handling the list manipulation
   - [`nanoid`](https://github.com/ai/nanoid) - package to generate random unique ids, just for adding and removing the dummy elements
- **Handling transitions**
   - [`react-transition-group`](https://reactcommunity.org/react-transition-group/) - "Exposes simple components useful for defining entering and exiting transitions" - package mantained by the react community
- **animation libraries**
   - [`Anime JS`](https://animejs.com/) - Very popular animation library with a flexible api and multiple options
   - [`Motion One JS`](https://motion.dev/) - lightweight and modern animation library made by the creator of `framer-motion` 
   - [`framer-motion`](https://www.framer.com/motion/) - Production-ready declarative animations.
   - [`react-flip-move`](https://github.com/joshwcomeau/react-flip-move) - Animation module that handles list animation (excluseve

Of course there are tons more of alternatives (react-spring, gsap, popmotion, velocity js, but I won't be covering all in this posts, I will cover the ones I prefer and I have worked before to achieve ~~nice~~ animations 

I have setup a repository and a deployment to vercel so you can check out the examples by yourself:
github: https://github.com/IrvingArmenta/animating-lists-react
example: https://animating-lists-react-cosmos.vercel.app/
![animatinglistgif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/67fb6125-a40b-4455-0bc0-f93fe2d90484.gif)

*experiment implementation with CRA and typescript*

I want to make clear that we won't manage animations with CSS at all. my main reason is the usage of callbacks and the async control to have over animations. There are callbacks for CSS animation yes, but they're not as common and doing duration management because cumbersome, that being said, let's continue.
## Animation Lists 
### Animejs + react-transition-group
First let's analyze the setup using `animejs` and `react-transition-group` 
Here it's a very simplified version  for the anime js list source code `AnimatedLists/lists/AnimeJsList.tsx`
[actual source code in github](https://github.com/IrvingArmenta/animating-lists-react/blob/main/src/global-components/AnimatedLists/lists/AnimeJsList.tsx)

```tsx
import { Transition, TransitionGroup } from 'react-transition-group';
import anime from 'animejs';

const AnimeJsList = () => {
  return (
    <ul>
      <TransitionGroup component={null}>
        {items.map((item, index) => {
          const liRef = createRef<HTMLLIElement>();
          return (
            <Transition
              timeout={{ enter: duration.enter, exit: duration.exit }}
              key={item.id}
              nodeRef={liRef}
              onEnter={() => {
                if (liRef.current && parentUlRef.current) {
                    anime({
                      targets: liRef.current,
                      opacity: [0, 1],
                      height: [0, liRef.current.offsetHeight],
                      marginTop: [0, 8],
                      duration: duration.enter
                    });
                  }
                }
              }}
              onExit={() => {
                if (liRef.current) {
                  anime({
                    targets: liRef.current,
                    duration: duration.exit,
                    opacity: 0,
                    height: 0,
                    marginTop: 0,
                    translateY: 0
                  });
                }
              }}
            >
              <li ref={liRef} key={item.id}>
                <NameHolder i={index} />
                <p>key: {item.id}</p>
                <button onClick={() => remove(item.id)}>
                  <DeleteIcon />
                </button>
              </li>
            </Transition>
          );
        })}
      </TransitionGroup>
    <ul/>
  );
};
```

As you can see, we are using `<TransitionGroup />` and `<Transition />` to wrap the `{items.map()}` array. Here we are listening for the `onEnter` and the `onExit` callback of the `<Transition />` component.

Before the usage of hooks `<Transition />` used to get the child node elements through the `findDomNode` api, which is now deprecated in [strict mode](https://reactjs.org/docs/strict-mode.html), so if you try to use it as before, it will throw a warning/error, therefore, you need to setup the usage by setting the `nodeRef={}` prop where the `ref` will be a a reference to the HTML node, in this case the `<li />` inside the list.

```tsx
const liRef = createRef<HTMLLIElement>();
...
<Transition nodeRef={liRef}  />
...
<>
  <li ref={liRef} key={item.id}>
     <NameHolder i={index} />
     <p>key: {item.id}</p>
     <button onClick={() => remove(item.id)}>
       <DeleteIcon />
     </button>
  </li>
</>
...
```

In order for the  reference to work for each `<li />` item, we need to use `createRef` inside the array map, since we are creating and removing elements `createRef` make these refs dynamically for us.
on both `onEnter` and `onExit` callbacks we are calling the `anime()` function to apply the JS animation:

```tsx
anime({
    targets: liRef.current,
    opacity: [0, 1],
    height: [0, liRef.current.offsetHeight],
    marginTop: [0, 8],
    duration: duration.enter
 });
```

`targets` it's the reference to the DOM of the `<li />` element, but we could target other elements inside the list, or other multiple selectors, like `#id` or `.class` selectors. 

We set all of the props to start with `[0]` and then animate to the end values, setting an array in the values, this it's equivalent to set the style immediately,  so` opacity` will start on `0` and animate to `1` , height will animate from `0` to the actual `<li />` offsetHeight, this is a way to achieve animation to 'auto' without setting a fixed height. 

All of these values will be animated at the same duration (800ms in the example) and the default `easing` by `animejs` 
> animejs default easing it's a weird "elastic" easing,  so sometimes it's hard to understand the animation timing, therefore I would recommend setting it to `linear` and try other easings from there. 
https://animejs.com/documentation/#linearEasing

the animation function has multiple callbacks that can help us orchestrate other side effects, such as `complete` , `update` and `begin` . these are extremely helpful when we want to handle events after an animation has concluded or when it has reached a certain point.

I won't be going super deep in explaining the functionality of each approach, so please if you're interested in `animejs` read their documentation: https://animejs.com/documentation

![animejs-stag.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/f099e81f-2aff-bee6-82ed-f396234a53f2.gif)


#### Let's review this approach:
--  **bundle size**  --
[bundlephobia - animejs](https://bundlephobia.com/package/animejs@3.2.1) - Minified and gzipped - `6.9kb`
[buundlephobia - react-transition-group](https://bundlephobia.com/package/react-transition-group@4.4.2) - Minified and gzipped - `4.4kb`
Total bundle size: `6.9kb` + `4.4kb` = `11.3kb`
**My opinion**
I think this approach for adding animation into your application gives you the flexibility to not just animate lists, but also other so many details, however, it lacks a lot of modern features, like dragging, sorting, and layout based animations, these have to be implemented by yourself, if you don't need such features, this is a very solid way to build some animation into your components! 

it also has a very well explained documentation and support from a big community.

---
### Motion JS + react-transition-group
This approach is very similar to the one above, so I won't be explaining a lot of it, there are some key differences though.
Here it's the simplfied version, just like the `animejs` one, file name is: `AnimatedLists/lists/MotionJsList.tsx` - [actual source code](https://github.com/IrvingArmenta/animating-lists-react/blob/main/src/global-components/AnimatedLists/lists/MotionJsList.tsx)

```tsx
import { animate } from 'motion';
import { Transition, TransitionGroup } from 'react-transition-group';

const MotionJsList = () => {
  return (
    <ul>
      <TransitionGroup component={null}>
        {items.map((item, index) => {
          const liRef = createRef<HTMLLIElement>();
          return (
            <Transition
              timeout={{ enter: duration.enter, exit: duration.exit }}
              key={item.id}
              nodeRef={liRef}
              onEntering={() => {
                if (liRef.current && parentUlRef.current) {
                    animate(
                      liRef.current,
                      {
                        opacity: [0, 1],
                        height: [0, `${liRef.current.offsetHeight}px`],
                        marginTop: [0, `${8}px`]
                      },
                      { duration: duration.enter / 1000 }
                    );
                  }
                }
              }}
              onExit={() => {
                if (liRef.current) {
                  animate(
                    liRef.current,
                    {
                      opacity: 0,
                      height: 0,
                      marginTop: 0
                    },
                    { duration: duration.exit / 1000 }
                  );
                }
              }}
            >
              <li ref={liRef} key={item.id}>
                <NameHolder i={index} />
                <p>key: {item.id}</p>
                <button onClick={() => remove(item.id)}>
                  <DeleteIcon />
                </button>
              </li>
            </Transition>
          );
        })}
      </TransitionGroup>
    </ul>
  );
};
```

As you may notice already, the setup is quite similar to the`animejs` one, however, the function that applies the animation for the node reference has a different name (~~anime~~ => `animate`)  and different syntax:

```js
animate(
      liRef.current,
      {
       opacity: [0, 1],
       height: [0, `${liRef.current.offsetHeight}px`],
       marginTop: [0, `${8}px`]
      },
      { duration: duration.enter / 1000 }
  );
```

`animate` takes the node reference as the first attribute, then followed by the actual animation values, these are quite similar to `animejs` in the sense that we are also setting `[0]` at the beggining to specify that we want to start from that point to the next at the end of the array.  

the `animate` function  do not take `number` (unless is `0`) as a valid type to animate to, it needs a unit `string`, just like we are doing here with the offsetHeight, `${liRef.current.offsetHeight}px`, if we don't do this, it won't know what value to apply, but it can be anything from `px` to `rem` to `em` etc.

Another thing to point at, it's that the `duration` value is set in a different object attribute, after the animation values, (`animejs` just put all of those in one single object), also, `duration` is measured in seconds and not milliseconds like `animejs` , therefore we have to divide the `800ms` by `1000` in order to get the `0.8s` value.

`Motion One` was made. by the actual creator of `framer-motion`, as a more web browser native experience, appealing to the (always changing) WAAPI (web animations api) it applies animations in the most performant and efficient way, using the `GPU` layers to not overload the main JS thread, and providing the most modern way of interacting with the WAAPI. 

![motion-js-thread.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/d89fbf19-e99e-ee36-821d-3ef19d37a025.gif)


> Animations with motion one do not depend in the main JS thread, you can see their example in their website where you can block Javascript and it won't interrupt the 60fps animation,
 more information here: https://motion.dev/guides/feature-comparison#hardware-acceleration


#### Let's review this approach:
--  **bundle size**  --
[bundlephobia - motion](https://bundlephobia.com/package/motion@10.4.0) - Minified and gzipped - `5.5kb`
[buundlephobia - react-transition-group](https://bundlephobia.com/package/react-transition-group@4.4.2) - Minified and gzipped - `4.4kb`
Total bundle size: `5.5kb` + `4.4kb` = `9.9kb`  ( `1.4kb` smaller than `animejs`)
**My opninion** 
If you're concerned about bundle size, I think this is the best approach in 2021, however, there is not much support from legacy browsers, like IE11 and the features are also lacking in comparison to animejs or gsap, therefore I'm not sure if it's ready for production. 

I would keep an eye for the improvement and any future releases, since I think it's becoming the most performant and updated package out there.

----
### Framer motion
framer motion it's a whole new different approach since it does not depend on `react-transition-group` to handle the animations, it has it's own way for watching exits and enterings of elements in a list, and we will see a simple setup for lists in the next code example: 

> This example uses `framer motion 4` since `create-react-app` has no support for `esm` packages and there are some conflicts, in `framer motion 5` the `AnimateSharedLayout` component is removed and any component with the `layout` prop works smartly without the wrap, more information: https://www.framer.com/docs/guide-upgrade/##esm-and-create-react-app

I will paste here the simplified version of the source code, but you can go to the [actual source code](https://github.com/IrvingArmenta/animating-lists-react/blob/main/src/global-components/AnimatedLists/lists/FramerMotionList.tsx) - `AnimatedLists/lists/FramerMotionList.tsx`

```tsx
import { AnimatePresence, AnimateSharedLayout, motion } from 'framer-motion';

const FramerMotionList = () => {
  return (
    <ul>
      <AnimateSharedLayout>
        <AnimatePresence initial={false}>
          {items.map((item, index) => {
            return (
              <motion.li
                key={item.id}
                initial={{ opacity: 0 }}
                animate={{
                  opacity: 1,
                  transition: { default: duration.enter / 1000 }
                }}
                exit={{
                  opacity: 0,
                  transition: { default: duration.exit / 1000 }
                }}
                layout={true}
                onAnimationStart={() => onFramerAnimationStart(item.id)}
              >
                <NameHolder i={index} />
                <p>key: {item.id}</p>
                <button onClick={() => remove(item.id)}>
                  <DeleteIcon />
                </button>
              </motion.li>
            );
          })}
        </AnimatePresence>
      </AnimateSharedLayout>
    <ul/>
  );
};
```

framer motion has it's own component to check for "presence", this handles the `exit` and `animate` states of the elements, it's called `<AnimatePresence />` 

framer motion animations need the `motion` helper to use as DOM references, this is how `framer motion` it's aware of the presence of these items, therefore the simple `<li />` became `<motion.li />` but you can render any DOM HTMLElement, such as `<motion.div />` or `<motion.ul />` etc.

`animate` works as the `onEnter` callback for new elements and `exit` works as the `onExit` callback for removed elements in the list. 

As you can see here, we are just defining an opacity animation, nothing else, and a duration of `0.8s` just like `motion js`, 

```tsx
 <motion.li
    key={item.id}
    initial={{ opacity: 0 }}
    animate={{
       opacity: 1,
       transition: { default: duration.enter / 1000 }
    }}
    exit={{
        opacity: 0,
        transition: { default: duration.exit / 1000 }
     }}
    layout={true}
    onAnimationStart={() => onFramerAnimationStart(item.id)}
 >
```

Instead of passing array values as previous examples (`[0, 8]`) we are setting the `initial={{ opacity: 0 }}` value from where the animation will happen when a new `<motion.li />` it's added, `animate={}` will animate it to `opacity: 1` and `exit={}` will animate to `0` when removed. 

Notice the `layout={true}` prop, in this case, we are listening for the changes in all the `motion.li` elements, in order for the list to smoothly re-arrange itself, if `layout` is set to `false` the elements will abruptly change, this is why, in this example, we don't need to set a `height` animation for a smooth transition, pretty convenient!
> `<motion />` elements have listeners for animation end and animation start, there is also an specific listener for the layout change animation, for more information: https://www.framer.com/docs/component/##animation-events

#### Let's review this approach:
--  **bundle size**  --
[bundlephobia - framer motion](https://bundlephobia.com/package/framer-motion@5.5.5) - Minified and gzipped - `41.9kb`
Total bundle size: `41.9kb`   - [`32kb` more than `motion js` and `react-transition-group` ] :scream: 

![scared-for-bundlesize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/3caf36a6-4432-dfb0-09c6-075b0d0afa04.gif)
<i>the face of bundle size phobia...</i>

Bundle size difference is huge :open_mouth: however, there are multiple alternatives to reduce the impact of the package, you can learn more here: https://www.framer.com/docs/guide-reduce-bundle-size/ 
the bundle size could be reduced to even `13kb`, however, it depends on the features you want to use, like `drag` and  dom interactions

**My Opinion**
As the package says itself, `framer-motion` it's a production ready animation library, it comes with a bundle of really nice features, a lot of seems like magic, and that's when some of the issues come up, there is a lot of "magic" going on that sometimes customizing to your needs, can become specially difficult.
In the other hand, it's extremely helpful and I woulnd't mind it using it in my next react project (I would use the lazy loading though) 

----
### React flip move 
`react-flip-move` it's an old (2016!) but still functional package (until certain degree...) 
In comparison with the other approaches, `react-flip-move` it's a library that has the main purpouse in animating lists with the [`Flip`](https://aerotwist.com/blog/flip-your-animations/#the-general-approach) (**F**irst, **L**ast, **I**nvert, **P**lay.) technique, the animation runs smoothly and the layout adapts itself.

Let's check the source code (remember, simplified source code!):
here it's the [actual code](https://github.com/IrvingArmenta/animating-lists-react/blob/main/src/global-components/AnimatedLists/lists/FlipMoveList.tsx) `AnimatedLists/lists/FlipMoveList.tsx`

```tsx
import FlipMove from 'react-flip-move';

const FlipMoveList = () => {
  return (
    <ul>
      <FlipMove typeName={null} onStart={(el) => onFlipMoveStart(el)}>
        {items.map((item, index) => {
          return (
            <li key={item.id}>
              <NameHolder i={index} />
              <p>key: {item.id}</p>
              <button onClick={() => remove(item.id)}>
                <DeleteIcon />
              </button>
            </li>
          );
        })}
      </FlipMove>
    </ul>
  );
};
```
Here you can see that the code for the `FlipMove` component it's the "simplest" one of all previoous examples, this is because `FlipMove` still uses the old `findDomNode` api, therefore, we will get the deprecation warning/error in strict mode.
 `FlipMove` also uses an old API, `componentWillReceiveProps` which has also already being deprectated and marked as `UNSAFE_` , so I don't think `FlipMove` it's available for production anymore, unless it gets mantained.

unlike the other libraries, `react-flip-move` does not have exactly defined styles for `enter` or `exit`, it uses various pre-defined animations and values for element transitions. You can set the next values: 
- easing 
- duration
- delay
- staggerDurationBy
- staggerDelayBy 

These gives you less specific control for the elements but the presets and the "experiment" here can give you an idea of how these values affect the animations : [Lab](https://joshwcomeau.github.io/react-flip-move/examples/#/laboratory?_k=o9bjw2)

```tsx
 <FlipMove typeName={null} onStart={(el) => onFlipMoveStart(el)}>
```
In the setup for `<FlipMove />` we have set the `typeName={null}` to null in order to not wrap the elements in any other HTML element. by default `<FlipMove />` renders a `<div />` that wraps the children.

It has multiple event listeners, one example is `onStart()` , however you need more implementation to get the `entering` element reference correctly, since it seems this event listens each element in the list.

`react-flip-move` and `framer motion` both support the animation of re-ordering the list out-of-the-box, no need for any kind of extra implementation. `animejs` and `motion` needs some way to understand the position of the elements and from there animate and properly move.  Here it's an article that may lead to that implementation, however I don't cover that in this blogpost -> https://itnext.io/animating-list-reordering-with-react-hooks-aca5e7eeafba

#### let's review this final approach: 
--  **bundle size**  --
[bundlephobia - react-flip-move](https://bundlephobia.com/package/react-flip-move@3.0.4) - Minified and gzipped - `5.5kb`
[buundlephobia - react-transition-group](https://bundlephobia.com/package/react-transition-group@4.4.2) - Minified and gzipped - `4.8kb`
Total bundle size: `4.8kb` [ the smallest alternative for lists animation in this post ]

**My Opinion** 
I think `react-flip-move` it's a great library for animating lists, the re-ordering feature comes included and animations are quite performant, however, at 2021, it has become stale and deprecation notices are flowing,  I'm not sure it will be available for `React 18`. In conclusion I wouldn't recommend it for future projects and would look for more a modern implementation if possible, it seems the creator encourage the usage of another library -> [react-flip-toolkit](https://github.com/aholachek/react-flip-toolkit) 
## Conclusion
As I said in the beginning, displaying lists of data is a common pattern in the front end work, and animation make these data to be more presentable and attractive to users.

 I always recommend implementing these kind of animations, it really changes the feeling for the end users when interacting with data, and I think it's also part of the idea of `micro-interactions` which I really love, perhaps I should write a blog post about it. 

I'll write a table with some points and features (good and bad) for each approach

*RTG = react-transition-group*

library/feature  | Anime JS + RTG | MotionJS + RTG | Framer motion | react flip move |
| :----- | :-------- | :-------- | :-------- | :-------- |
Bundle size | 11.3kb   | 9.9kb    |    41.9kb     |  4.8kb
SSR (no style flashing) |   :x:  | :white_check_mark: |  :white_check_mark:  |  :x:
Flexibility (usage besides lists) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x:
Good documentation | :white_check_mark: | :warning:  <br />(some docs are not exposed ) | :white_check_mark: | :x:
Simple setup | :x: | :x: | :white_check_mark: | :white_check_mark: |
Best GPU handling | :x: | :white_check_mark: | :x: | :white_check_mark: | 
List re-ordering animation | :x: | :x: | :white_check_mark: | :white_check_mark: |

For more specifics in `motion` differences, they have this page right here:
 https://motion.dev/guides/feature-comparison.

----

![thanks-american.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286690/24f1fe37-a57d-4bbe-c78c-5ad6e2b7df40.gif)

Thank you very much for your time and the interest in this post, let me know if you have any interest in animation an which approach it's the better one for you? (of course I think depends on the project and needs!) 

Here are again, both github links and the deployment in vercel for this simple post!
github: https://github.com/IrvingArmenta/animating-lists-react
example: https://animating-lists-react-cosmos.vercel.app/

Irving.