Portal을 이용하여 Modal 구현하기 | Next.js

Bori·2024년 1월 7일
0

Next.js

목록 보기
10/12
post-thumbnail

Portal

React의 portal은 컴포넌트를 렌더링 할 때 UI를 어디에 렌더링 시킬지 DOM을 사전에 선택할 수 있도록 합니다.
이를 통해 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드에 렌더링을 할 수 있습니다.

Portal을 이용하여 Modal 구현

모달의 경우 일반적으로 최상위에 나타납니다. 따라서 portal을 이용하여 모달이 다른 컴포넌트의 최상위에 나타날 수 있도록 모달이 렌더링 될 위치를 지정했습니다.

먼저 index.html에 다음과 같이 모달이 렌더링 될 DOM 노드를 추가해줍니다.

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Payhere Frontend 과제</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!-- NOTE: 모달 루트 추가 -->
    <div id="modal-root"></div> 
  </body>
</html>

createPortal

Portal을 생성하기 위해 createPortal을 다음과 같이 호출합니다.

createPortal(children, domNode, key?) 
  • children : Portal을 통해 렌더링 할 JSX와 같은 React로 렌더링할 수 있는 모든 것
  • domNode : children이 렌더링 될 DOM 노드
  • key? : Portal의 키로 사용할 고유한 문자열 또는 숫자(optional)
  • createPortal의 반환값
    • JSX에 포함될 수 있거나 React 컴포넌트에서 반환될 수 있는 React 노드를 반환합니다.
    • children으로 전달 받은createPortal의 반환값을 domNode에 위치시킵니다.

다음과 같이 createPortal을 이용하여 모달을 적용했습니다.

interface ModalProps {
  children: ReactNode;
  isVisible: boolean;
}

const Modal = ({ children, isVisible }: Props) => {
  const modalRoot = document.getElementById('modal-root') as HTMLElement;

  return createPortal(
    isVisible ? (
      <ModalContainer>
        <ModalOverlay />
        <ModalContent>{children}</ModalContent>
      </ModalContainer>
    ) : null,
    modalRoot
  );
}

export default Modal;

Next.js에서 Portal 사용하기

Next.js에는 index.html 파일이 없기 때문에 _document.tsx에 모달 루트를 추가합니다.

import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="en">
      <Head />
      <body>
        <Main />
        // NOTE: 모달 루트 추가
        <div id="modal-root" />
        <NextScript />
      </body>
    </Html>
  );
}

_app.tsx에 모달 루트를 추가해도 동작합니다. 다만 그 위치가 다음과 같이 차이가 있습니다.

  • _document.tsx<div id="modal-root" /> 추가
    • <div id="__next">와 같은 계층에 위치
  • _app.tsx<div id="modal-root" /> 추가
    • <div id="__next">의 하위 계층에 위치

document is not defined

React와 동일하게 코드를 적용할 경우 Next.js에서는 document is not defined 에러가 발생합니다.
그 이유는 많은 분들이 알고 있듯 서버 사이드 렌더링을 먼저 수행하기 때문입니다.
따라서, 클라이언트 사이드에 존재하는 window, document 같은 브라우저 전역 객체를 사용할 수 없습니다.

useEffect를 이용하여 document 객체가 정의된 후 사용할 수 있도록 다음과 같이 코드를 작성합니다.

interface ModalProps {
  children: ReactNode;
  isVisible: boolean;
}

const Modal = ({ children, isVisible }: Props) => {
  const [modalElement, setModalElement] = useState<HTMLElement | null>(null);

  useEffect(() => {
    const modalRoot = document.getElementById('modal-root');

    setModalElement(modalRoot);
  }, []);

  if (modalElement === null) return null;

  return createPortal(
    isVisible ? (
      <ModalContainer>
        <ModalOverlay />
        <ModalContent>{children}</ModalContent>
      </ModalContainer>
    ) : null,
    modalRoot
  );
}

export default Modal;

참고

0개의 댓글