Using React Modal with hooks: Trying to animate in mobile slide up and down. Slide up works wonderfully, but slide down style doesn't get applied

2.1k views Asked by At

This animation portion works really well on slideUp. But the slideDown animation (close) doesn't get triggered. There is an x on the modal that closes it. Is it because of the hook? This is the second time i'm using hook and first time to create a custom hook so was pretty stoked when it actually worked! But I'm stuck on the close animation part.

I tried using transition animation, but that didn't work even for slide up, so decided to move to keyframes.

Any help is appreciated. Thanks!

Component:

import React from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';
import classNames from 'classnames';
import styles from './styles.scss';

const Modal = ({isVisible, hide, header, children}) => {
  const closeStyles = classNames('close-icon', styles.modalClose);
  const headerStyles = classNames('h3', styles.header);
  const slideAnimation = isVisible ? styles.slideUp : styles.slideDown;
  const className = classNames(styles.commonModalStyles, slideAnimation)

  return (
    <ReactModal
      isOpen={isVisible}
      className={className}
      overlayClassName={styles.overlay}
      contentLabel={header}
    >
      <div className={styles.headerContainer}>
        <h3 className={headerStyles}>{header}</h3>
        <div className={closeStyles} onClick={hide}/>
      </div>
      <div className={styles.modal}>{children}</div>
    </ReactModal>
  );
};

Modal.propTypes = {
  isVisible: PropTypes.bool,
  hide: PropTypes.func,
  header: PropsTypes.string,
  children: PropTypes.node,
};

Modal.defaultProps = {
  isVisible: false,
};

export default Modal;

Custom Hook:

import { useState } from 'react';

const useToggle = () => {
  const [isVisible, setIsVisible] = useState(false);

  function toggle(event) {
    event.preventDefault();
    setIsVisible(!isVisible);
  }

  return {
    isVisible,
    toggle,
  }
};

export default useToggle;

Usage:

// import from file and render inside functional component.

  const {isVisible, toggle} = useToggle();
 <ModalV2 isVisible={isVisible} hide={toggle} header="Custom Header"> some content </Modal>

My css code:

.overlay {
  overflow: auto;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

.slideUp {
  animation: slideup .2s ease-in-out;
}

.slideDown {
  animation: slidedown .2s linear;
}

@keyframes slideup {
  0% {transform: translateY(100%);}
  100% {transform: translateY(0);}
}

@keyframes slidedown {
  0% {transform: translateY(0);}
  100% {transform: translateY(100%);}
}

.commonModalStyles {
  overflow: auto;
  position: fixed;
  width: 100%;
  height: 100%;
}
1

There are 1 answers

0
Daniel Duong On

I wrote a custom hook to solve this issue at work.

import { useState, useEffect, useRef, useLayoutEffect } from "react";

type UseTransition = (toggle: boolean) => [any, boolean];

const useTransition: UseTransition = toggle => {
  const didMountRef = useRef(false);
  const componentRef: any = useRef(null);
  const [isAnimating, setIsAnimating] = useState(false);

  useLayoutEffect(() => {
    if (didMountRef.current) {
      setIsAnimating(true);
    } else {
      didMountRef.current = true;
    }
  }, [toggle]);

  useEffect(() => {
    componentRef.current.addEventListener("transitionend", () => {
      setIsAnimating(false);
    });
  }, [componentRef, setIsAnimating]);

  return [componentRef, isAnimating];
};

export default useTransition;

See it in action here:

https://codesandbox.io/s/peaceful-grothendieck-42zij