Alan Bryan
Alan Bryan

Reputation: 11

React Modal Transition working on exit, but not on entrance

I created a custom Modal component in React for creating popup modal dialogs. I am using react-transition-group to try and "zoom-in" the modal when it first shows up and "zoom-out" when it is closed.

When the modal is closed, it is properly zooming out and the effect is working. However, when the modal first pops up, it is not zooming in as expected. It's just instantly showing up on the screen.

Here is my modal component JSX code:

import { useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import styled from 'styled-components';

import createModalPortal from './createModalPortal';

const DimBackground = styled.div`
  background-color: rgba(0, 0, 0, 0.7);
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 0;
`;

const Centered = styled.div`
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
`;

const StyledModal = styled.div`
  width: ${(props) => props.$width};
  height: ${(props) => props.$height};
  background: ${(props) => props.$backgroundColor};
  color: ${(props) => props.$textColor};
  z-index: 10;
  border-radius: 0.8rem;
  box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.04);
  padding: 1rem;
  display: flex;
  flex-direction: column;
`;

export default function Modal({
  children,
  isOpen = false,
  backgroundColor = 'var(--white-100)',
  textColor = 'var(--gray-800)',
  width = 'auto',
  height = 'auto',
  onModalClose,
  closeOnBackgroundClick = true,
  closeOnEscapeKey = true,
  zoomAnimation = true
}) {
  const nodeRef = useRef(null);
  const [modalKey, setModalKey] = useState(0);

  useEffect(() => {
    function handleEscapeKey(e) {
      if (e.code === 'Escape' && closeOnEscapeKey) {
        onModalClose();
      }
    }

    if (closeOnEscapeKey) {
      document.addEventListener('keydown', handleEscapeKey);
    }
    return () => {
      document.removeEventListener('keydown', handleEscapeKey);
    };
  }, [closeOnEscapeKey, onModalClose]);

  useEffect(() => {
    if (isOpen) {
      setModalKey((prevKey) => prevKey + 1);
    }
  }, [isOpen]);

  return createModalPortal(
    <CSSTransition
      in={isOpen}
      timeout={{ enter: 800, exit: 800 }}
      classNames={zoomAnimation ? 'zoom' : ''}
      unmountOnExit
      nodeRef={nodeRef}
      key={modalKey}
      onEnter={() => console.log('Entering')}
      onEntered={() => console.log('Entered')}
      onExit={() => console.log('Exiting')}
      onExited={() => console.log('Exited')}
    >
      <DimBackground
        ref={nodeRef}
        onClick={
          closeOnBackgroundClick
            ? (e) => {
                e.stopPropagation();
                onModalClose();
              }
            : null
        }
      >
        <Centered onClick={(e) => e.stopPropagation()}>
          <StyledModal $backgroundColor={backgroundColor} $textColor={textColor} $width={width} $height={height}>
            {children}
          </StyledModal>
        </Centered>
      </DimBackground>
    </CSSTransition>
  );
}

Also I have added these classes to my global CSS file:

.zoom-enter {
  transform: scale(0.1);
  opacity: 0.01;
}

.zoom-enter-active {
  transform: scale(1);
  opacity: 1;
  transition: transform 800ms, opacity 800ms;
}

.zoom-exit {
  transform: scale(1);
  opacity: 1;
}

.zoom-exit-active {
  transform: scale(0.1);
  opacity: 0.01;
  transition: transform 800ms, opacity 800ms;
}

Just in case my portal code is wrong, here is the createModalPortal.js file too:

import ReactDOM from 'react-dom';

const createModalPortal = (children, containerId = 'modal-root') => {
  let container = document.getElementById(containerId);

  if (!container) {
    container = document.createElement('div');
    container.id = containerId;
    document.body.appendChild(container);
  }

  return ReactDOM.createPortal(children, container);
};

export default createModalPortal;

Upvotes: 0

Views: 29

Answers (0)

Related Questions