본문 바로가기
차근차근

React에서 모달 시스템 제대로 설계하기|Context 전역 관리부터 Portal까지 실전 구현

by 아빠고미 2025. 4. 28.
반응형

🪟 React에서 모달(Dialog) 시스템 제대로 설계하기|Context로 상태 관리부터 포탈 구현까지

안녕하세요, 퍼블리셔 노미입니다!
웹에서 흔히 볼 수 있는 UI 중 하나인 모달 창(Modal Dialog)은 사용자에게 알림을 주거나, 확인을 받거나, 추가 정보를 보여주는 데 자주 사용됩니다.


React에서 모달을 구현할 땐 단순히 `display: block`으로 보여주는 것을 넘어서 Context로 전역 관리하거나, React Portal로 구조를 분리하거나, 다중 모달 & 접근성까지 고려해야 할 부분이 많아요.
오늘은 그런 모달 시스템을 기초부터 실전 패턴까지 정리해드릴게요.


📌 모달이란?

모달(Modal)은 현재 UI 흐름을 중단하고, 사용자에게 특정 작업을 유도하는 오버레이 창입니다.
주로 배경을 어둡게 하고, 가운데에 작은 박스를 띄우는 형태로 사용되죠.

  • 경고창 / 확인창
  • 로그인 / 회원가입 팝업
  • 이미지 확대 보기

🔧 기본 모달 컴포넌트 구조


// Modal.js
export default function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return (
    <div className="overlay">
      <div className="modal">
        <button onClick={onClose}>닫기</button>
        {children}
      </div>
    </div>
  );
}

스타일 예시 (CSS)


.overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  max-width: 500px;
  width: 100%;
}

📡 props로 제어하는 기본 사용 예시


function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>모달 열기</button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <p>모달 내용입니다</p>
      </Modal>
    </>
  );
}

→ 기본적인 구현은 위처럼 state를 사용해서 show/hide 처리할 수 있어요.


🌍 Context로 전역 모달 시스템 만들기

1. 컨텍스트 생성


const ModalContext = createContext();

export function ModalProvider({ children }) {
  const [modal, setModal] = useState(null);

  const openModal = (content) => setModal(content);
  const closeModal = () => setModal(null);

  return (
    <ModalContext.Provider value={{ openModal, closeModal }}>
      {children}
      {modal && (
        <div className="overlay">
          <div className="modal">
            <button onClick={closeModal}>닫기</button>
            {modal}
          </div>
        </div>
      )}
    </ModalContext.Provider>
  );
}

export function useModal() {
  return useContext(ModalContext);
}

2. 어디서든 호출하기


function AnyComponent() {
  const { openModal } = useModal();

  return (
    <button onClick={() => openModal(<p>모달 내용입니다</p>)}>
      모달 열기
    </button>
  );
}

→ Context를 활용하면 어느 컴포넌트에서도 모달을 열고 닫을 수 있어요.


🧭 React Portal로 구조 분리

모달은 종종 DOM 트리 상 다른 곳에 위치해야 올바르게 렌더링됩니다. 이때 사용하는 것이 React Portal입니다.

index.html에 모달 전용 DOM 추가

<body>
  <div id="root"></div>
  <div id="modal-root"></div>
</body>

Modal.js 수정


import ReactDOM from "react-dom";

export default function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <div className="overlay">
      <div className="modal">
        <button onClick={onClose}>닫기</button>
        {children}
      </div>
    </div>,
    document.getElementById("modal-root")
  );
}

→ 포탈을 통해 DOM 구조를 깨끗하게 분리 가능


🧠 모달에서 자주 발생하는 UX 이슈

  • ESC 키로 닫기
  • 배경 클릭 시 닫기
  • 모달 열리면 스크롤 고정
  • 모달 내부 탭 포커스 이동 제한

ESC로 닫기


useEffect(() => {
  const handleEsc = (e) => {
    if (e.key === "Escape") onClose();
  };
  window.addEventListener("keydown", handleEsc);
  return () => window.removeEventListener("keydown", handleEsc);
}, []);

📚 마무리하며

모달은 단순히 열고 닫는 UI 요소가 아닙니다.
기획 단계부터 어디서나 열 수 있어야 하고, 구조 분리가 되어야 하며, 접근성과 UX까지 고려해야 제대로 된 모달 시스템이라고 할 수 있어요.

오늘 배운 방식들을 토대로 여러분만의 모달 매니저 시스템을 만들어보세요!
다음 글에서는 다중 모달, 중첩 모달 설계 또는 애니메이션 모달 컴포넌트화를 다뤄볼게요.


#React모달 #모달설계 #Context전역관리 #ReactPortal #모달UX #모달컴포넌트 #퍼블리셔노미 #리액트UI설계 #프론트엔드실전

반응형