Magnus Engdal
Magnus Engdal

Reputation: 5604

Elements doesn't survive state change for css transitions in React

I have a highscore list that updates automatically with current scores once every minute.

It then updates the component with this.setState({ data: newData });.

Below I sort the new data by score, run each item through map that updates their style.top.

Javascript

...

let current = this.state.data.sort((a,b) => {
  if(a.score < b.score) return -1;
  if(a.score > b.score) return 1;
  return 0;
});

let items = current.map((item,i) => {
  return (
    <div
      key={item.name}
      className="item"
      style={{ top: (i*30) + 'px', backgroundColor: item.color }}>
        {item.name}
    </div>
  );
});

return (
  <div>
    {items}
  </div>
);

CSS

.item {
  position: absolute;
  transition: top 1s;
}

I want the items to animate up and down to their new positions, but this doesn't happen. The browser seems to remember parts of the DOM, but not others.

React seems to understand which item is which by using the unique key, if I inspect using React Developer Tools, but the actual DOM doesn't.

Even stranger, only items moving up the list (higher score) seems to be remembered. Items moving down is recreated, and therefor the transition doesn't work. They just pop into existence without transitioning to their new position.

Here is a working example to demonstrate. (JSFiddle)

Upvotes: 2

Views: 640

Answers (2)

James Brierley
James Brierley

Reputation: 4670

If you change it so that the items are not reordered between render cycles, but are always rendered in the same order with different top styles, it animates correctly. I think this is because changing the order of elements forces them to be removed from the DOM and re-added by react.

The relevant code is:

render() {
    let current = this.state.data.slice().sort((a,b) => {
        if(a.score < b.score) return -1;
        if(a.score > b.score) return 1;
        return 0;
    });

    let items = this.state.data.map(item => {
        let position = current.indexOf(item);
        return (
          <div
            key={item.name}
            className="item"
            style={{ top: (position*30) + 'px', backgroundColor: item.color }}>
                {item.name}
          </div>
        );
    });

    return (
      <div>
        {items}
      </div>
    );
}

Updated demo

Upvotes: 3

Jesper Bylund
Jesper Bylund

Reputation: 933

React redraws the DOM elements you update. This will resolve in really odd behaviours, such as blinking, jumping etc.

The React team offered a couple of solutions for this before they started working on Fiber instead. With the release of Fiber I hope we'll see better support for transitions and animations.

Until that time, I recommend you use external libs for animations such as: https://github.com/FormidableLabs/react-animations For general animation.

And https://github.com/reactjs/react-transition-group for elements entering and exiting the DOM.

Upvotes: 1

Related Questions