Animate when items in a list change order with React?

Viewed 9387

I have a list of items. When the order changes I want them to animate to their new position.

Before:

<ul>
  <li>One</li>
  <li>Two</li>
</ul>

After:

<ul>
  <li>Two</li>
  <li>One</li>
</ul>

Are there any libraries that can do this? Ive tired React Transition Group, React Pose and React Spring but none seem to support it, instead they focus on animations when items enter and leave the DOM. Im a bit surprised I haven't found anything as it seems like a common use case to me.

https://reactcommunity.org/react-transition-group/

https://popmotion.io/pose/

https://www.react-spring.io/

1 Answers

In react-spring there is an example about it. But it is quite complicated, there is a lot happening. I created a simplified version from it.

You have an array of names. You define a y value based on the index. And you can move the elements with translate property. The position is set to absolute.

With one click you can shuffle the array. With another click you can remove elements. In react transition, you can define the entering and leaving animation. The leaving animation called when you remove an element.

import { render } from 'react-dom';
import React, { useState } from 'react';
import { useTransition, animated } from 'react-spring';
import shuffle from 'lodash/shuffle';
import './styles.css';

let data = [
  {
    name: 'Rare Wind'
  },
  {
    name: 'Saint Petersburg'
  },
  {
    name: 'Deep Blue'
  },
  {
    name: 'Ripe Malinka'
  },
  {
    name: 'Near Moon'
  },
  {
    name: 'Wild Apple'
  }
];

function App() {
  const [rows, set] = useState(data);
  let height = 20;
  const transitions = useTransition(
    rows.map((data, i) => ({ ...data, height, y: i * height })),
    d => d.name,
    {
      from: { position: 'absolute', height: 20, opacity: 0 },
      leave: { height: 0, opacity: 0 },
      enter: ({ y, height }) => ({ y, height, opacity: 1 }),
      update: ({ y, height }) => ({ y, height })
    }
  );

  return (
    <div class="list" style={{ height }}>
      <button onClick={() => set(shuffle(rows))}>click</button>
      <button onClick={() => set(rows.slice(1))}>remove first</button>
      {transitions.map(({ item, props: { y, ...rest }, key }, index) => (
        <animated.div
          key={key}
          class="card"
          style={{
            zIndex: data.length - index,
            transform: y.interpolate(y => `translate3d(0,${y}px,0)`),
            ...rest
          }}
        >
          <div class="cell">
            <div class="details">{item.name}</div>
          </div>
        </animated.div>
      ))}
    </div>
  );
}

const rootElement = document.getElementById('root');
render(<App />, rootElement);

Here is the sandbox: https://codesandbox.io/s/animated-list-order-example-with-react-spring-teypu

Edit: I added add element as well, because it is a nicer example this way. :)

Update: This sandbox is from necrifede who updated my example to version 9.: https://codesandbox.io/s/animated-list-order-example-with-react-spring-forked-nhwqk9?file=/src/index.js

Related