The simplest React mount/unmount animation with material UI and emotion
Tags: material, react, animation, css
Published: 2021-11-08 14:17:26

Surely you want sometimes in react to animate an entrance and/or exit of unmounting component. Instead of using a library, there is a nice way to do it by yourself, just with material ui!

For this we will make use of the emotion css prop and the [keyframes helper](https://emotion.sh/docs/keyframes). 

As of material ui we can just take the Box component

Our goal is to create an Animated component that can receive following props:
`show`: if the component is mounted or not
`mountData`: describing the entrance animation
`mountData.keyframes`: Standard css animation keyframes
`mountData.time`: Animation duration in seconds 
`mountData.type`: Css animation type (e.g. linear, ease-out...)
`unmountData`: describing the exit animation
`unmountData.keyframes`: Standard css animation keyframes
`unmountData.time`: Animation duration in seconds 
`unmountData.type`: Css animation type (e.g. linear, ease-out...)
`unmountTimeout` (optional): to provide a possibility for auto unmount the component after a timeout
`setShow` (optional): function to unmount the component, provided by the parent

If you don't provide the last two, the parent component will control the whole mount/unmount process.

And here is the solution:

```JS
import { Box } from '@mui/material';
import { useEffect, useState } from 'react';
import { css, keyframes } from '@emotion/react';

const defaultMountData = {};

const Animated = ({
  children,
  show,
  setShow,
  mountData = defaultMountData,
  unmountData = defaultMountData,
  unmountTimeout,
  ...rest
}) => {
  const [animationData, setAnimationData] = useState(null);
  const { time, type = 'linear' } = animationData || {};

  const animationCss = animationData?.keyframes ?
    css`animation: ${keyframes`${animationData.keyframes}`} ${time}s ${type}`
    : '';

  useEffect(() => {
    let mounted = true;
    let handler = null;
    let unmountHandler = null;

    if (show) {
      setAnimationData(mountData);
      if (unmountTimeout && setShow) {
        unmountHandler = setTimeout(() => mounted && setShow(false), unmountTimeout);
      }
    } else if (animationData) {
      const { time: unmountTime } = unmountData;
      handler = setTimeout(() => mounted && setAnimationData(null), unmountTime * 1000);
      setAnimationData(unmountData);
    }

    return () => {
      handler && clearTimeout(handler);
      unmountHandler && clearTimeout(unmountHandler);
      mounted = false;
    };

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mountData, unmountData, show]);

  if (!animationData) return null;

  return (
    <Box
      css={animationCss}
      component="div"
      {...rest}
    >
      {children}
    </Box>
  );
};

export default Animated;
```

We can not use default props for our default mount data, because it will cause all the time re-rendering.

Now in our component we place the mountData with the settings for entrance animation, the unmountData with settings for exit animation. As soon the show param become `true`, that will activate our Animated component entrance animation. After 4 seconds the exit animation will be played, and will set the shouldBeMounted variable to `false`, which will unmount the component:

```JS
const [shouldBeMounted, setShouldBeMounted] = useState(false);

<Animated
  show={shouldBeMounted}
  mountData={{
    keyframes: `
       0% {opacity: 0}
       100% {opacity: 1}
    `,
    time: 0.3,
  }}
  unmountData={{
    keyframes: `
      0% {opacity: 1}
      100% {opacity: 0}
    `,
    time: 0.8,
  }}
  unmountTimeout={4000}
  setShow={setShouldBeMounted}
>
  Text to hide with animation
</Animated>
```

If we don't want automated unmounting, we can just ignore the unmountTimeout and the setShow params. If we don't want entrance or exit animation, we can also just ignore the mountData/unmountData:

```JS
const [shouldBeMounted, setShouldBeMounted] = useState(false);

<Animated
  show={shouldBeMounted}
  unmountData={{
    keyframes: `
      0% {opacity: 1}
      100% {opacity: 0}
    `,
    time: 0.8,
  }}
>
  Text to hide with animation
</Animated>
```

Here, we control totally our Animated component from the parent, and we don't use any animation for the mount, we use animation only for unmount.

Well that's it!

This is a simple, fast and lightweight way to create mount animations just using css.

Best Regards
Anton Tonchev
JUST-SELL.online