ThePHPAddicted
ThePHPAddicted

Reputation: 313

Animation not triggering when using material UI

I encountered an issue when adding material UI to my already existing application. Basically what's happening is that when I add material UI components to my modals, the entering animation of the modal does not trigger. Downgrading material UI to 1.0.0 or removing all MUI components solves the issue. Also, using any other UI library does not cause this issue.

https://codesandbox.io/s/mui-animation-issue-mgph3

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Test from "./Test";

const Overlay = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 10000;
`;

const Modal = styled.div`
  position: absolute;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100px;
  height: 100px;
  align-items: center;
  background: white;
`;

const modalsComponentLookupTable = {
  Test
};

const ModalContainer = ({ setModals, children }) => {
  const [modalStyle, setModalStyle] = useState({
    opacity: 0,
    transition: "opacity 2000ms",
    willChange: "opacity",
    transitionTimingFunction: "cubic-bezier(0.165, 0.840, 0.440, 1.000)"
  });
  const [bgStyle, setBgStyle] = useState({
    background: "rgba(0, 0, 0, 1)",
    willChange: "opacity",
    transition: "opacity 2000ms cubic-bezier(0.165, 0.840, 0.440, 1.000)",
    opacity: 0
  });
  const unMountStyle = () => {
    // css for unmount animation
    setModalStyle({
      opacity: 0,
      transition: "opacity 2200ms",
      willChange: "opacity",
      transitionTimingFunction: "cubic-bezier(0.165, 0.840, 0.440, 1.000)"
    });
    setBgStyle({
      background: "rgba(0, 0, 0, 1)",
      willChange: "opacity",
      transition: "opacity 2200ms cubic-bezier(0.165, 0.840, 0.440, 1.000)",
      opacity: 0
    });
  };

  const mountStyle = () => {
    // css for mount animation
    setModalStyle({
      opacity: 1,
      transition: "opacity: 2000ms",
      willChange: "opacity",
      transitionTimingFunction: "cubic-bezier(0.165, 0.840, 0.440, 1.000)"
    });

    setBgStyle({
      willChange: "opacity",
      opacity: 1,
      background: "rgba(0, 0, 0, 1)",
      transition: "opacity 2000ms cubic-bezier(0.165, 0.840, 0.440, 1.000)"
    });
  };

  useEffect(() => {
    mountStyle();
  }, []);

  const back = e => {
    e.stopPropagation();
    unMountStyle();
    setTimeout(() => setModals([]), 2200);
  };

  return (
    <Overlay onClick={back} style={bgStyle}>
      <Modal style={modalStyle}>{children}</Modal>
    </Overlay>
  );
};

const ModalsManager = ({ modals, setModals }) => {
  const renderedModals = modals.map(modalDescription => {
    const ModalComponent = modalsComponentLookupTable[modalDescription];

    return (
      <ModalContainer setModals={setModals}>
        <ModalComponent />
      </ModalContainer>
    );
  });

  return <span>{renderedModals}</span>;
};

export default ModalsManager;

When the Test component contains any kind of MUI components, the entering animation doesn't trigger. No error in the console. Apparently it's something in my code that does trigger this issue since I already opened an issue on their github and they said it's not an issue with their library: https://github.com/mui-org/material-ui/issues/17888

Upvotes: 2

Views: 8102

Answers (1)

Ryan Cogswell
Ryan Cogswell

Reputation: 80986

I have isolated this sufficiently to be convinced that it is not a problem with Material-UI, but rather a brittleness in the approach used for your transition animation.

All I have to do to break it is to include a component that immediately re-renders on mount (either in componentDidMount or useLayoutEffect). This is something that TransitionGroup does and TransitionGroup is used by TouchRipple which is used by ButtonBase which is used by several Material-UI components such as button.

Changing your Test component to the following was sufficient to cause the bad behavior:

import React from "react";

const RerenderOnMount = () => {
  const [, setMyState] = React.useState(false);
  React.useLayoutEffect(() => {
    setMyState(true);
  }, []);
  return null;
};
const Test = () => {
  return (
    <div
      css={`
        height: 100px;
        width: 100px;
        z-index: 5;
      `}
      onClick={e => e.stopPropagation()}
    >
      <div>
        Test
        <RerenderOnMount />
      </div>
    </div>
  );
};

export default Test;

Edit MUI Animation Issue

In the example above which still reproduces your problem, no Material-UI components are being used.

I believe the re-render of the child element is impacting (delaying) the timing of when the browser first tries to paint the modal and is causing the browser to not recognize that a transition has occurred when you call mountStyle (i.e. behaving the same as if you had used the "mount" styles initially rather than recognizing a transition from the default styles to the mount styles). The trickiness in getting this timing correct in React to ensure the browser does a transition is the reason why people typically use react-transition-group to help with this (as Material-UI does).

I was able to get your code to work by calling mountStyle via setTimeout within the useEffect, but I can't guarantee that this hack won't have issues in other cases (depending on what is in the modal) or different browsers, and I would instead recommend reworking the transition to let react-transition-group manage the entering/exiting states. Here's the working version with my setTimeout hack: https://codesandbox.io/s/mui-animation-issue-505rj.

Upvotes: 5

Related Questions