사실 전에 만들었던 훅은 useModal은 문제점을 하나 가지고 있었다.
두번째 모달이 열린후 닫히면 첫번째 모달의 애니메이션이 다시 재생되는 버그였다.
매우 간단한다 !!
모달의 상태를 부모 컴포넌트에서 관리하고, Modal에 주입해서 사용하게 변경해주면 된다.
하지만 그랬을때 모달이 10개라면 state도 10개를 작성해줘야 하고 번거롭다.
그래서 한곳에서 모달의 상태들을 관리해 주고 getter, setter를 나눠서 사용하려한다.
그리고 상태관리를 위해 요즘 익히고 있는 recoil을 사용할 것이다.
$ npm install recoil
...
root.render(
<RecoilRoot>
<App />
</RecoilRoot>
);
interface ModalAtom {
name: string;
isOpen: boolean;
willUnmount: boolean;
}
const modalAtom = atom<ModalAtom[]>({
key: 'modalAtom',
default: [],
});
export default modalAtom;
기존에 state를 부모 컴포넌트에서 받아와 썼는데 이제 recoil atom에서 받아오게 수정한다.
interface ModalProps {
animationMs?: number;
openAnimation?: Keyframes;
closeAnimation?: Keyframes;
onClose: () => void;
children: React.ReactNode;
modalName: string;
}
const Modal = ({
animationMs,
openAnimation,
closeAnimation,
onClose,
children,
modalName,
}: ModalProps) => {
const modalState = useRecoilValue(modalAtom);
const nowModal = modalState.find(modal => modal.name === modalName);
const handleClickInnerModal = (e: MouseEvent<HTMLDivElement>) => {
// ModalWrapper로 이벤트 전파 방지
e.stopPropagation();
};
return (
<>
{nowModal?.isOpen && (
<ModalPortal>
<ModalWrapper onClick={onClose}>
<ModalInner
onClick={onClose}
openAnimation={openAnimation}
closeAnimation={closeAnimation}
isUnmount={nowModal?.willUnmount || false}
animatinoMs={animationMs}
>
<ModalContent onClick={handleClickInnerModal}>
{children}
</ModalContent>
</ModalInner>
</ModalWrapper>
</ModalPortal>
)}
</>
);
};
export default React.memo(Modal);
interface useModalProps {
openAnime?: Keyframes;
closeAnime?: Keyframes;
animeTimeMs?: number;
modalName: string;
onClose?: () => void;
onShow?: () => void;
}
const useModal = ({
openAnime = Up100,
closeAnime = Down100,
animeTimeMs = 300,
modalName,
onClose,
onShow,
}: useModalProps) => {
const setModalState = useSetRecoilState(modalAtom);
const closeModal = useCallback(() => {
setModalState(prev =>
prev.map(modal =>
modal.name === modalName ? { ...modal, willUnmount: true } : modal,
),
);
setTimeout(() => {
setModalState(prev => prev.filter(modal => modal.name !== modalName));
onClose && onClose();
}, animeTimeMs);
}, []);
const showModal = useCallback(() => {
setModalState(prev => [
...prev,
{ name: modalName, willUnmount: false, isOpen: true },
]);
onShow && onShow();
}, []);
const Modal = ({ children }: { children: React.ReactNode }) => (
<ModalFrame
onClose={closeModal}
openAnimation={openAnime}
closeAnimation={closeAnime}
modalName={modalName}
>
{children}
</ModalFrame>
);
return { Modal, showModal, closeModal };
};
export default useModal;
이제 애니메이션이 정상적으로 작동하게 됐다.