reactjsanimationframer-motion

Framer Motion Exit Animation Not Working in React Modal Component


I'm using Framer Motion to create a modal component in React. While the opening animation works perfectly, the closing animation does not. The modal abruptly disappears without the intended animation.

I've tried different positionings and modes for AnimatePresence, but nothing seems to help. I've also looked at other Stack Overflow answers related to this issue, but none of them resolved my problem.

Here's my code:

import React, { ReactNode } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import styles from './StickyModal.module.scss';

interface StickyModalProps {
  isOpen: boolean;
  onClose: () => void;
  children: ReactNode;
  title: string;
}

const StickyModal: React.FC<StickyModalProps> = ({
  isOpen,
  onClose,
  title,
  children,
}) => {
  return (
    <AnimatePresence mode="wait">
      {isOpen && (
        <motion.div
          className={styles.backdrop}
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          onClick={e => {
            if (e.target === e.currentTarget) {
              onClose();
            }
          }}
        >
          <motion.div
            className={styles.modal}
            initial={{ y: '100%' }}
            animate={{ y: 0 }}
            exit={{ y: '100%' }}
            transition={{ type: 'spring', stiffness: 200, damping: 20 }}
          >
            <span>{title}</span>
            <button className={styles.closeButton} onClick={onClose}>
              Закрыть
            </button>
            {children}
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
};

export default StickyModal;

Here are the relevant CSS styles:

.backdrop {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100lvh;
  overflow-y: auto;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  border: none;
  z-index: 1000;
}

.modal {
  background-color: white;
  padding: 50px 20px;
  width: 100%;
  max-height: 70vh;
  border-radius: 30px 30px 0 0;
  position: fixed;
  bottom: -10px;
  left: 0;
  //overflow: hidden;
}

.closeButton {
  margin-top: 10px;
}

How can I fix the exit animation for the modal? Any insights or suggestions would be greatly appreciated. Thank you!

UPD

In an abbreviated form, the code in which the modal window is called looks like this

import { useState } from 'react';

export interface IResumeStatisticExtended {
  linkTitle: string | null;
  link: string | null;
}
const StatsTable = () => {
  const [selectedItem, setSelectedItem] = useState<IResumeStatisticExtended | null>(null);

  const handleAction = (index: number): void => {
    setSelectedItem(modifiedArrayStats[index]);
  };

  const closeModal = (): void => {
    setSelectedItem(null);
  };

  return (
    <div>
      <StatsTableColumn
        array={renderColumnItems('status')}
        title="Подробнее"
        hasError={modifiedArrayStats.some(hasError)}
        onAction={handleAction}
      />

      {selectedItem && (
        <StickyModal
          isOpen={true}
          onClose={closeModal}
          title={formatDateTime(selectedItem.date_time)}
        >
          <div>
            <p>{selectedItem.fact}</p>
          </div>
        </StickyModal>
      )}
    </div>
  );
};

export default StatsTable;

Solution

  • The problem is with the way the modal is conditionally rendered with selectedItem &&. Framer motion can only animate unmounting items when wrapped in <AnimatePresence>. But when the <AnimatePresence> itself is unmounted (when selectedItem is set to null), the modal will disappear without animating.

    To solve this issue, you should rely on your modal's isOpen prop instead of conditionally rendering it:

      return (
        <div>
          <StatsTableColumn
            array={renderColumnItems('status')}
            title="Подробнее"
            hasError={modifiedArrayStats.some(hasError)}
            onAction={handleAction}
          />
            <StickyModal
              isOpen={selectedItem !== null}
              onClose={closeModal}
              title={formatDateTime(selectedItem.date_time)}
            >
              <div>
                <p>{selectedItem.fact}</p>
              </div>
            </StickyModal>
        </div>
      );