What's a react.js-friendly way to animate a list-reordering?

I just released a module to tackle exactly this problem

https://github.com/joshwcomeau/react-flip-move

It does a few things differently than Magic Move / Shuffle:

  1. It uses the FLIP technique for hardware-accelerated 60FPS+ animations
  2. It offers options to "humanize" the shuffle by incrementally offsetting delay or duration
  3. It handles interruptions gracefully, no weird glitch effects
  4. Bunch of other neat stuff like start/finish callbacks

Check out the demos:

http://joshwcomeau.github.io/react-flip-move/examples/#/shuffle


React Shuffle is solid and up to date. It's inspired by Ryan Florences Magic Move demo

https://github.com/FormidableLabs/react-shuffle


I realise this is a bit of an old question and you've probably found a solution by now, but for anyone else coming across this question, check out the MagicMove library by Ryan Florence. It's a React library to handle the exact scenario you describe: https://github.com/ryanflorence/react-magic-move

See it in action: https://www.youtube.com/watch?v=z5e7kWSHWTg#t=424

Edit: This is broken in React 0.14. See React Shuffle as an alternative, as suggested by Tim Arney below.


I didn't want to use a third-part library for my animations so I implemented the "FLIP" animation technique using React lifecycle methods getSnapshotBeforeUpdate() and componentDidUpdate().

When the list item's props.index changes the old position is captured in getSnapshotBeforeUpdate() and in componentDidUpdate() the css transform: translateY() is applied so that the item appears to be animating from old position to current position.

class TreeListItem extends Component {

  constructor(props) {
    super(props);
    this.liRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps) {
    if (prevProps.index !== this.props.index) {
      // list index is changing, prepare to animate
      if (this.liRef && this.liRef.current) {
        return this.liRef.current.getBoundingClientRect().top;
      }
    }
    return null;
  }


  componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevProps.index !== this.props.index && snapshot) {
      if (this.liRef && this.liRef.current) {
        let el = this.liRef.current;
        let newOffset = el.getBoundingClientRect().top;
        let invert = parseInt(snapshot - newOffset);
        el.classList.remove('animate-on-transforms');
        el.style.transform = `translateY(${invert}px)`;
        // Wait for the next frame so we
        // know all the style changes have
        // taken hold.
        requestAnimationFrame(function () {
          // Switch on animations.
          el.classList.add('animate-on-transforms');
          // GO GO GOOOOOO!
          el.style.transform = '';
        });
      }
    }
  }

  render() {
    return <li ref={this.liRef}>
    </li >;
  }
}

class TreeList extends Component {

  render() {
    let treeItems = [];
    if (this.props.dataSet instanceof Array) {
      this.props.dataSet.forEach((data, i) => {
        treeItems.push(<TreeListItem index={i} key={data.value} />)
      });
    }

    return <ul>
      {treeItems}
      </ul>;
  }
}
.animate-on-transforms {
  /* animate list item reorder */
  transition: transform 300ms ease;
}

The best way I can think of to accomplish this off the top of my head would be to first make sure that you give every one of your elements a key as described here:

http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Next I would define CSS3 animation similar to this:

Animating lists with CSS3

Basically in your render function you would calculate where the element is suppose to go, ReactJS will place the element where it's suppose to be (make sure you give each element the same key every time! This is important to make sure ReactJS reuses your DOM element properly). In theory at least, the CSS should take care of the rest of the animation for you outside of reactjs/javascript.

Disclamer... I've never actually tried this ;)