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