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;
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>
);