DEV Community

a-tonchev
a-tonchev

Posted on

The simplest mount/unmount animation with material UI and emotion

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.

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:

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;
Enter fullscreen mode Exit fullscreen mode

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:

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>
Enter fullscreen mode Exit fullscreen mode

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:

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>
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)