Search code examples
reactjsreact-spring

React / React Spring List Animate Out Not Working


I'm experimenting with react-spring trying to apply animations on a list of components on mount/unmount using the <Transition> component. The animation happens as expected on mount but doesn't happen at all on unmount–the removed components appear to immediately unmount without animation.

I suspect I'm misunderstanding how keys work as it seems to be the only thing that differs in my code versus the examples—I'm using the id property from each object an array. My assumption is that it's the same as React's built-in key used for lists of components, just passed all at once. I've tried passing the data array using Transition's items argument and setting key to be a function, but it just malfunctions in a different way.

I set up a simple demo here, where I made a list and set a timeout to remove the first item after 3 seconds—here's the code:

import React, { Component } from "react";
import { animated, config, Transition } from "react-spring";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        {
          id: 1,
          text: "This is item 1"
        },
        {
          id: 2,
          text: "This is item 2"
        },
        {
          id: 3,
          text: "This is item 3"
        }
      ]
    };
  }

  componentDidMount() {
    const { items } = this.state;
    setTimeout(() => {
      this.setState({
        items: items.slice(1)
      });
    }, 3000);
  }

  render() {
    const { items } = this.state;

    return (
      <div className="App">
        <ul>
          <Transition
            native
            keys={items.map(item => item.id)}
            config={config.slow}
            from={{ opacity: 0 }}
            to={{ opacity: 1 }}
          >
            {items.map(item => styles => {
              return <animated.li style={styles}>{item.text}</animated.li>;
            })}
          </Transition>
        </ul>
      </div>
    );
  }
}

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

And I've it running on a CodeSandbox here: https://codesandbox.io/s/r5n8v3x85q

Am I missing something?


Solution

  • I got it working with the following code. You were missing the "leave" property on the Transition component. Also you can pass in "transition" for the properties in the Transition component, it will give you much nicer looking animations.

    import React, { Component } from "react";
    import { animated, config, Transition } from "react-spring";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          items: [
            {
              id: 1,
              text: "This is item 1"
            },
            {
              id: 2,
              text: "This is item 2"
            },
            {
              id: 3,
              text: "This is item 3"
            }
          ]
        };
      }
    
      componentDidMount() {
        const { items } = this.state;
        setTimeout(() => {
          this.setState({
            items: items.slice(1)
          });
        }, 3000);
      }
    
      render() {
        const { items } = this.state;
    
        return (
          <div className="App">
          <button onClick={() => this.setState({items: []})}>Remove List items</button> 
            <ul>
              <Transition
                native
                keys={items.map(item => item.id)}
                config={config.slow}
                from={{ opacity: 0, transition: "opacity .25s ease"  }}
                to={{ opacity: 1, transition: "opacity .25s ease"  }}
                leave={{ opacity: 0, transition: "opacity .25s ease" }}
              >
                {items.map(item => styles => {
                  return <animated.li style={styles}>{item.text}</animated.li>;
                })}
              </Transition>
            </ul>
          </div>
        );
      }
    }