useModal 애니메이션 개선

Song-Minhyung·2023년 3월 25일
0

React

목록 보기
9/10

시리즈

  1. React 모달 여러개 띄우기
  2. useModal훅 만들기, 애니메이션 주기
  3. useModal 애니메이션 개선

이전에 있던 문제

사실 전에 만들었던 훅은 useModal은 문제점을 하나 가지고 있었다.
두번째 모달이 열린후 닫히면 첫번째 모달의 애니메이션이 다시 재생되는 버그였다.

🪛 문제 수정

매우 간단한다 !!
모달의 상태를 부모 컴포넌트에서 관리하고, Modal에 주입해서 사용하게 변경해주면 된다.
하지만 그랬을때 모달이 10개라면 state도 10개를 작성해줘야 하고 번거롭다.
그래서 한곳에서 모달의 상태들을 관리해 주고 getter, setter를 나눠서 사용하려한다.
그리고 상태관리를 위해 요즘 익히고 있는 recoil을 사용할 것이다.

1. 설치를 위해 아래 명령어를 친다.

$ npm install recoil

2. index.tsx에 RecoilRoot을 추가해준다

...
root.render(
  <RecoilRoot>
	  <App />
  </RecoilRoot>
);

3. modalAtom을 작성해준다.

interface ModalAtom {
  name: string;
  isOpen: boolean;
  willUnmount: boolean;
}

const modalAtom = atom<ModalAtom[]>({
  key: 'modalAtom',
  default: [],
});

export default modalAtom;

4. Modal 컴포넌트를 수정한다.

기존에 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);

5. useModal에서 열릴때, 닫힐때 recoil의 atom을 변경한다.

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;

완료

이제 애니메이션이 정상적으로 작동하게 됐다.

profile
기록하는 블로그

0개의 댓글