Skip to content

useModal

Choi Jeongmin edited this page Dec 1, 2024 · 1 revision

📄 useModal

개발 중 모달을 보다 쉽게 제어하고 활용할 수 있도록 만든 과정을 기록합니다.

커스텀 훅을 통해 모달을 제어하여, 모달의 생성부터 제거까지 간단하게 사용할 수 있도록 한 내용을 포함하고 있습니다.

🧩 배경 및 필요성

‘Ask-It' 에서는 UI에서 모달을 통해 다양한 상호작용을 하고 있습니다. 사용자들에게 적절한 피드백을 제공하거나 추가적인 정보를 요구하는 등 다양한 상황에서 모달이 활용됩니다.

모달 사용이 빈번할 것으로 예상되는 상황에서, 효율적으로 모달을 활용하기 위한 고민이 필요했습니다. 복잡한 모달 로직을 매번 개별 컴포넌트에 구현하는 대신, 모달을 손쉽게 관리하고 재사용할 수 있는 접근 방식이 필요했습니다.

요구 사항은 아래와 같습니다.

  1. 다양한 UI를 가진 컴포넌트들이 모달로 쉽게 사용될 수 있어야 한다.
  2. 모달이 표시될 때, 추가적인 요소들이 별도의 설정 없이 자동으로 적용되어야 한다 (예: 오버레이).

위 요구 사항을 충족하면서 모달의 사용을 간편하게 할 수 있는 무언가가 필요했습니다.

🗺️ 문제 해결 과정

모달의 복잡한 로직을 간소화하고, 다른 컴포넌트들이 쉽게 사용할 수 있도록 커스텀 훅을 만들었습니다.

설계 구조

  • 모달의 상태를 관리하기 위해 내부적으로 useState 훅을 사용하여 모달의 열림/닫힘 상태를 제어합니다.
  • openModal, closeModal 함수를 통해 모달을 여닫는 기능을 제공하고, 모달을 열 때마다 별도의 ModalContext를 통해 모달 내부에서 상태에 쉽게 접근하고 조작할 수 있도록 했습니다.
graph TD;
  A[Component] -->|useModal Hook| B[Modal State];
  B --> C{isOpen};
  C -->|True| D[Render Modal];
  C -->|False| E[Do Nothing];
  D --> F[ModalContext];
  F --> G[Background & Content];
Loading

구현 과정

아래는 핵심 부분의 코드입니다.

// modal.context.ts
import { createContext } from 'react';

export interface ModalContextProps {
  openModal: () => void;
  closeModal: () => void;
}

export const ModalContext = createContext<ModalContextProps | undefined>(
  undefined,
);
// modal.hook.tsx
import { ReactNode, useContext, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';

import Background from '@/features/modal/Background';
import { ModalContext } from '@/features/modal/modal.context';

export const useModal = (children: ReactNode) => {
  const [isOpen, setIsOpen] = useState(false);

  const openModal = () => setIsOpen(true);

  const closeModal = () => setIsOpen(false);

  const contextValue = useMemo(() => ({ openModal, closeModal }), []);

  const Modal = useMemo(() => {
    if (!isOpen) return null;
    return createPortal(
      <ModalContext.Provider value={contextValue}>
        <Background>{children}</Background>
      </ModalContext.Provider>,
      document.body,
    );
  }, [isOpen, children, contextValue]);

  return {
    Modal,
    openModal,
    closeModal,
  };
};

export const useModalContext = () => {
  const context = useContext(ModalContext);
  if (!context) {
    throw new Error('useModalContext must be used within a ModalProvider');
  }
  return context;
};
  • createPortal을 사용하여 모달 컴포넌트를 document.body에 렌더링함으로써, 모달의 DOM 계층 구조와 상관없이 최상위에 표시합니다.
  • ModalContext.Provider를 통해 모달 내부의 컴포넌트들이 모달을 쉽게 닫을 수 있도록 컨텍스트를 활용했습니다.

📈 결과 및 성과

  • 모달 로직을 여러 컴포넌트에서 반복적으로 구현하는 대신, 커스텀 훅을 사용함으로써 재사용 가능한 코드로 만들 수 있었습니다. 이를 통해 코드의 중복을 줄일 수 있었습니다.
  • 커스텀 훅을 통해 모달의 상태 관리를 모달을 사용하는 각 UI 컴포넌트 외부로 분리함으로써, 해당 컴포넌트들의 복잡성을 줄이고 상태 관리의 책임을 명확하게 분리할 수 있었습니다.
  • useModal 훅을 통해 모달을 쉽게 열고 닫을 수 있는 함수를 제공함으로써, 모달을 사용하는 컴포넌트에서의 코드가 간결해졌습니다. 복잡한 상태 관리 로직 없이 간단하게 훅 하나로 해결할 수 있어 개발 시간을 단축할 수 있었습니다.
  • 모달이 열릴 때 오버레이를 자동으로 추가하고, 모달이 닫히는 액션이나 상태를 일관되게 관리함으로써 예측 가능한 상태를 유지할 수 있었습니다.
Clone this wiki locally