Modal을 열 때는 애니메이션이 적용이 되는데, 닫을 때는 적용이 되지 않는다.

무엇이 원인일까? 현재 우리 프로젝트는 useModal훅을 만들어 modal을 관리하고 있다. 이 덕분에 여러 개의 Modal을 띄울 수도 있다.
export default function useModal() {
const { addModal, removeModal } = useModalContext();
const [id] = useState(() => String(elementId++));
useDidUnMount(() => removeModal(id));
return {
open: (modalElement: ReactElement) => {
addModal(id, modalElement);
},
close: () => {
removeModal(id);
},
};
}
open메소드를 사용하면 addModal, close메소드를 사용하면 removeModal를 실행한다.
addModal은 modalMap에 값을 추가하는 함수이다.
const addModal = useCallback((id: string, element: ReactNode) => {
setModalMap((prev) => {
const cloned = new Map(prev);
cloned.set(id, element);
return cloned;
});
}, []);
removeModal은 modalMap에서 값을 제거하는 함수이다.
const removeModal = useCallback((id: string) => {
setModalMap((prev) => {
const cloned = new Map(prev);
cloned.delete(id);
return cloned;
});
}, []);
우리는 framer-motion라이브러리로 애니메이션을 넣고 있다.
// Modalwrapper.client.tsx
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed left-1/2 top-0 z-modal h-full w-full max-w-450 -translate-x-1/2 bg-[rgba(0,0,0,0.4)]"
>
<div ref={modalRef} className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
{children}
</div>
</motion.div>
여기서, 왜 Modal 아니라 ModalWrapper에 motion을 주는 지 알아야 한다.
Modal에 motion을 넣을 경우, Modal에만 애니메이션이 적용되어 바깥 배경(ModalWrapper)은 애니메이션 없이 먼저 띄어지고 그 후에 Modal만 애니메이션과 함께 천천히 띄어진다.
공식문서를 보면, AnimatePresence는 제거되지 않고 그 안에서 노드가 mount/unmount되어야 한다.


React tree에서 제거가 될 때 애니메이션을 넣어주는 AnimatePresence를, mount되고 unmount되어지는 ModalWrapper컴포넌트 위에 넣어주어야 한다. ModalWrapper가 mount되고 unmount되어지는 컴포넌트는 ModalProvider이다.
<ModalContext.Provider value={context}>
{children}
{Array.from(modalMap.entries()).map(([id, element]) => (
<React.Fragment key={id}>{element}</React.Fragment>
))}
</ModalContext.Provider>
여기서 Modal(엄밀히 말하면 ModalWrapper)가 추가되는 {Array..}를 감싸주면 된다.
<ModalContext.Provider value={context}>
{children}
<AnimatePresence>
{Array.from(modalMap.entries()).map(([id, element]) => (
<React.Fragment key={id}>{element}</React.Fragment>
))}
</AnimatePresence>
</ModalContext.Provider>