createPortal – React

ModalPortal컴포넌트는 안에 들어 있는 children요소를 레이아웃 상단에 있는 div요소에 createPortal이라는 것을 이용해 연결시켜줍니다. 그렇게 함으로써 돔트리상 가장 말단에 있는 요소일지라도 우리가 보여주고 싶은 그 상위까지 연결시켜줄 수 있습니다.

1. layout에 d가 portal인 div요소를 하나 만들어줍니다.

app> layout.tsx

const openSans = Open_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={openSans.className}>
      ...
      <div id="portal" /> 
    </html>
  );
}

2. reactDom.createPortal을 이용해 ModalPortal컴포넌트 생성

import reactDom from "react-dom";

type Props = {
  children: React.ReactNode;
};

export default function ModalPortal({ children }: Props) {
  // 브라우저 환경일 때만 렌더링(브라우저 환경이 아니라면 null반환)
  if (typeof window === "undefined") {
    return null;
  }

  const node = document.getElementById("portal") as Element;
  return reactDom.createPortal(children, node);
}

3. MdalPortal을 사용하는 부분에서 상태를 만들어 사용.

//1. 상태를 관리함으로 클라이언트 컴포넌트임을 명시
"use client";
import ModalPortal from "@/components/ui/ModalPortal";

export default function PostListCard({ post, priority = false }: Props) {
	
	//2. 모달 상태를 관리하기 위한 useState를 사용.
  const [openModal, setOpenModal] = useState(false);
  return (
    <article className="rounded-lg shadow-md border border-gray-200">
      <div className="flex items-center p-2">
        <Avatar image={userImage} highlight size="medium" />
        <span className="text-gray-900 font-bold ml-2">{username}</span>
      </div>
      <Image
        className="w-full object-cover aspect-square"
        src={image}
        alt={`photo by ${username}`}
        width={500}
        height={500}
        priority={priority}
			{/*  3. 상태값 관리 */}
        onClick={() => setOpenModal(true)}
      />
      <ActionBar
        username={username}
        createdAt={createdAt}
        likes={likes}
        text={text}
      />
      <CommentForm />

			{/*  3.  로직작성 */}
      {openModal && (
        <ModalPortal>
          <div className="fixed top-0 left-0 w-full h-full bg-slate-500 z-50">
            포스트 상세 페이지!!
          </div>
        </ModalPortal>
      )}
    </article>
  );
}

4. 외부적인 UI를 담당하는PostModal 컴포넌트 생성

검은색 반투명에 close버튼을 가지고 있는 다이얼로그 UI

PostModal.tsx

import React from "react";
import CloseIcon from "@/components/ui/icons/CloseIcon";

type Props = {
  children: React.ReactNode;
  onClose: () => void;
};
export default function PostModal({ onClose, children }: Props) {
  return (
    <section
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          // 외부 section 부분을 클릭한 경우
          onClose();
        }
      }}
    >
      <button onClick={() => onClose()}>
        <CloseIcon />
      </button>
      {children}
    </section>
  );
}