Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use React Material UI's transition components to animate adding an item to a list?

I have this class.

class Demo extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      items: []
    };

    this.add = this.add.bind(this);
    this.clear = this.clear.bind(this);
  }

  add() {
    this.setState(prev => {
      const n = prev.items.length;
      return {
        items: [<li key={n}>Hello, World {n}!</li>, ...prev.items]
      };
    });
  }

  clear() {
    this.setState({ items: [] });
  }

  render() {
    return (
      <div>
        <div>
          <button onClick={this.add}>Add</button>
          <button onClick={this.clear}>Clear</button>
        </div>

        {/* This is wrong, not sure what to do though... */}
        <Collapse in={this.state.items.length > 0}>
          <ul>{this.state.items}</ul>
        </Collapse>
      </div>
    );
  }
}

Sandbox link: https://codesandbox.io/s/material-demo-ggv04?file=/Demo.js

I'm trying to make it so that every time I click the "add" button, a new item gets animated into existence at the top of the list and the existing items get pushed down. Not sure how to proceed though.

Extra Resources

  • Example of what I'm trying to achieve: https://codeburst.io/yet-another-to-do-list-app-this-time-with-react-transition-group-7d2d1cdf37fd
  • React Transition Group Transition docs: http://reactcommunity.org/react-transition-group/transition (which seem to be used internally by Collapse)
like image 597
425nesp Avatar asked Jun 23 '20 23:06

425nesp


People also ask

Can I use framer motion with material UI?

Material-UI has component prop option for this kind of requirement and should do better and more elegant approach than the wrapping-approach. Then, you can pass any props provided by framer-motion package to your Material-UI component as long as they're not conflicted (i'm not sure if any).

How do you add transitions in MUI?

create(props, options) => transition.


1 Answers

I updated your Sandbox code to achieve what you wanted, but I don't think MaterialUI is the best library for that (I could be missing a better way to do it).

The challenge is that when you add a new item, that doesn't exist in the DOM yet. And most of those animation libraries/components require the element to be in the DOM and they just "hide" and "show" it with a transition time.

I had a similar situation and after some research, the better library I found that can handle animation for elements that are not yet in the DOM, was the Framer Motion. (You can check their documentation for mount animations)

Anyway, here is the link for the new Code Sandbox so you can take a look. The changes I made:

Removed random key

In the map function that creates your list using the <Collapse /> component, there was a function to get a random integer and assign that as a key to your component. React needs to have consistent keys to properly do its pretenders, so removing that random number fixes the issue where your "Toggle" button wasn't animating properly. (If your list of items doesn't have an unique ID, just use the index of the map function, which is not a good solution, but still better than random numbers).

<Collapse key={i} timeout={this.state.collapseTimeout} in={this.state.open}>
    {it}
</Collapse>

Added a new function to control the toggle

The approach here was: add the item in your list and, after the element is in the DOM, close the <Collapse />, wait a little bit and open it again (so you can visually see the animation). In order to do that, we needed a new "toggle" function that can explicit set the value of the collapse.

toggleValue(value) {
  this.setState(() => {
    return {
      open: value
    };
  });
}

Added a variable timeout for the collapse

The last issue was that, closing the <Collapse /> when the new item is added, was triggering the animation to close it. The solution here was to dynamically change the timeout of the collapse, so you don't see that.

setCollapseTimeout(value) {
  this.setState(() => {
    return {
      collapseTimeout: value
    };
  });
}

When adding the element to the list, wait to trigger the animation Again, to work around the issue with elements not yet in the DOM, we need to use a setTimeout or something to wait to toggle the <Collapse />. That was added in your add() function.

add() {
  this.toggleValue(false);
  this.setCollapseTimeout(0);
  this.setState(prev => {
    const n = prev.items.length;
    return {
      items: [<li key={n}>Hello, World {n}!</li>, ...prev.items]
    };
  });
  setTimeout(() => {
    this.setCollapseTimeout(300);
    this.toggleValue(true);
  }, 100);
}

Again, this is a hacky solution to make <Collapse /> from MaterialUI work with elements that are not yet in the DOM. But, as mentioned, there are other libraries better for that.

Good luck :)

like image 87
Bruno Monteiro Avatar answered Sep 21 '22 08:09

Bruno Monteiro