문제
-
Modal을 열 때는 애니메이션이 적용이 되는데, 닫을 때는 적용이 되지 않는다.
해결 과정
1. useModal 분석
-
무엇이 원인일까? 현재 우리 프로젝트는
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;
});
}, []);
2. framer-motion이 문제가 아닐까?
우리는 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>
원인
React tree에서 제거가 될 때 애니메이션을 넣어주는 AnimatePresence를 추가해주지 않아서 애니메이션이 없었던 것이다.
해결 후
결론
AnimatePresence가 무엇인지도 모르고 framer-motion라이브러리를 사용하고 있엇다. 이런 애니메이션으로 정말 앱처럼 만들 수 있는 가능성이 무궁무진하다는 생각을 하였다. 항상 어떤 애니메이션을 넣으면 자연스러울까 생각해봐야겠다.