[TIL] 2023. 02. 09. 모달창의 전역 상태 관리를 어떻게 할 것인가?

suno·2023년 2월 9일
0
post-thumbnail

구현하고자 하는 것

  • 로그인/회원가입 시 사용되는 모달
  • 일반적인 모달과 다르게, 모달 내에서 페이지 이동이 가능해야 함. (로그인 → 프로필 변경 → 설문조사 → 완료)

구현 결과

modal


개선 이전 모달

✔️ Modal 컴포넌트

const Modal = ({ isOpen, width, height, children }: props) => {
  return (
    <ModalBackDrop isOpen={isOpen}>
      <ModalContainer isOpen={isOpen} width={width} height={height}>
        {children}
      </ModalContainer>
    </ModalBackDrop>
  );
};

✔️ useModal 상태 관리 hook

const useModal = (initialValue: boolean) => {
  const [isOpen, setIsOpen] = useState<boolean>(initialValue);

  const handleOpenButtonClick = (): void => {
    setIsOpen(true);
  };

  const handleCloseButtonClick = (): void => {
    setIsOpen(false);
  };

  return { isOpen, handleOpenButtonClick, handleCloseButtonClick };
};
  • useModal로 로직을 분리했지만, 페이지에서 훅을 불러와야하므로 전역 상태관리가 불가능함.
  • 상위 컴포넌트에서 모달을 상태관리를 할 수는 있지만, prop drilling이 발생할 수 있음.
  • Modal의 props로 children을 받아와 렌더링 함.

‼️ 여기서 문제점

  • 모달 상태는 페이지별로 관리하지 않고, 전역 상태로 관리되어야 함.
  • 로그인 모달의 경우 페이지를 넘기듯 모달의 상태를 단계별로 관리해야 하는데, 데이터 구조가 적합하지 않음.

개선한 모달 (작업중)

✔️ 라우터 내 최상위 컴포넌트로 ModalContainer를 위치함.

// router.ts

const Router = () => {
  return (
    <BrowserRouter>
      <Header />
      <ModalContainer />
      <Suspense fallback={<div>...loading</div>}>
        <Routes>
          ...
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

✔️ 모달을 전역 상태로 관리하기 위해 recoil atom을 추가함.

// recoil/atoms.ts

type ModalState = {
  isOpen: boolean;
  height: string;
  width: string;
  type: string;
  page?: number;
};

export const modalState = atom<ModalState>({
  key: 'modalState',
  default: { isOpen: false, height: '400px', width: '400px', type: '' },
});

‼️ 커스텀 훅으로 모달 열기/닫기, 사이즈 조절 함수를 추가함.

// hooks/useLoginModa.ts

import { useRecoilState } from 'recoil';
import { modalState } from '../recoil/atoms';

const useLoginModal = () => {
  const [modal, setModal] = useRecoilState(modalState); // global state

  const { isOpen } = modal;

	// !! 모달을 열 때 페이지 유형과 페이지 넘버를 받아옴.
  const openModal = (type: string, page: number): void => {
    setModal({
      ...modal,
      isOpen: true,
      type,
      page,
    });
  };

  const closeModal = (): void => {
    setModal({
      ...modal,
      isOpen: false,
    });
  };

  const updateModalSize = (width: string, height: string): void => {
    setModal({
      ...modal,
      width,
      height,
    });
  };

  return { isOpen, modal, openModal, closeModal, updateModalSize };
};

export default useLoginModal;

✔️ Header 컴포넌트에서 모달 오픈 이벤트

<NavItemLi onClick={() => openModal('login', 0)}>
  로그인하기
</NavItemLi>

✔️ ModalContainer 컴포넌트에서 페이지 유형에 따라 컴포넌트 렌더링

// components/ModalContainer.tsx

export default function ModalContainer() {
  const { isOpen, width, height, type } = useRecoilValue(modalState);

  return isOpen ? (
    <BackDrop>
      <Container width={width} height={height}>
        {type === LOGIN && <LoginModal />}
      </Container>
    </BackDrop>
  ) : null;
}

✔️ LoginModal 컴포넌트에서 페이지 넘버에 따라 창 사이즈를 조절 & 컴포넌트 렌더링

// components/login/LoginModal.tsx

export default function LoginModal() {
  const {
    modal: { page },
    updateModalSize,
  } = useLoginModal();

  // 페이지에 따라 모달 크기 조절
  useEffect(() => {
    if (page === 0) {
      updateModalSize('41.0625rem', '31.4375rem');
    } else if (page === 1) updateModalSize('44.0625rem', '27.25rem');
  }, [page]);

  if (page === 0) return <LoginPage0 />;
  if (page === 1) return <LoginPage1 />;
  if (page === 2) return <LoginPage2 />;
  if (page === 3) return <LoginPage3 />;
  if (page === 4) return <LoginPage4 />;

  return <div>modal</div>;
}

Reference

[React] 전역상태를 이용해 모달을 띄우는 게시물을 만들어보자

profile
Software Engineer 🍊

0개의 댓글