index.html에 dom요소들과 같은 위치에 생성하고 해당 모달 요소의 z-index를 통해서 뒤에 배경을 가리고 모달을 보여주는식으로 접근.
lauout.tsx
<html lang="ko">
<body className={inter.className}>
<div className="px-40 py-4">
<header className="flex justify-between">
<p className="text-2xl font-bold flex justify-start">
<Link href={"/"}>Evelog</Link>
</p>
</header>
{children}
</div>
<div id="modal"></div> // 모달 아이디
</body>
</html>
createPortal에는 인수를 2개를 필수로 전달해야하는데 (key는 선택 사항)
모달에 보여줄 자식요소와 모달로 사용할 domNode를 전달해야함
import reactDOM from "react-dom";
import React from "react";
type Props = {
children: React.ReactNode;
};
const ModalPortal = ({ children }: Props) => {
if (typeof window === "undefined") {
return null;
}
const Dom = document.getElementById("modal") as Element;
return reactDOM.createPortal(children, Dom);
};
위에서 아이디를 지정한 요소를 getElementById로 불러와서 연결해주고
createPortal에 요소로 전달
모달의 내용 밖을 클릭하거나 x표시를 클릭할때 모달이 닫히게 하기위한 컴포넌트 파일
(close는 모달을 보여주는 페이지에서 설명)
해당 컴포넌트는 modalPortal과 children사이에 위치함
import React from "react";
type Props = {
children: React.ReactNode;
Close: () => void;
};
const CloseModal = ({ Close, children }: Props) => {
return (
<div
className="fixed inset-0 hidden bg-gray-500 bg-opacity-90 transition-opacity md:block z-50 h-full"
onClick={(e) => {
if (e.target === e.currentTarget) {
Close();
}
}}
>
<button
onClick={() => Close()}
className="mx-auto w-[70vw] flex justify-end relative top-32 right-14 font-bold text-3xl"
>
X
</button>
{children}
</div>
);
};
export default CloseModal;
import React from "react";
const ModalPage = () => {
return (
<div className="w-[30vw] mx-auto h-full flex items-center overflow-hidden px-4">
<section className="flex w-[28vw] mx-auto items-center justify-center rounded-xl bg-white shadow-2xl sm:px-6 sm:pt-8 md:p-6 lg:p-8">
모달페이지입니다
</section>
</div>
);
};
export default ModalPage;
모달을 테스트하기위해 임의로 modalTest라는 페이지를 생성 (client page로 생성해야함)
"use client";
import ModalPortal from "@/components/1.ModalPortal";
import CloseModal from "@/components/2.CloseModal";
import ModalPage from "@/components/4.ModalPage";
import React, { useState } from "react";
const ModalTest = () => {
const [openModal, setOpenModal] = useState(false);
return (
<div>
ModalTest
<hr />
<button
className="w-24 h-24 p-2 rounded-lg mt-4 bg-slate-300"
onClick={() => setOpenModal(!openModal)}
>
open modal
</button>
{openModal && (
<ModalPortal>
<CloseModal Close={() => setOpenModal(false)}>
<ModalPage />
</CloseModal>
</ModalPortal>
)}
</div>
);
};
export default ModalTest;
해당 페이지에서 close 메소드를 생성하는데 close는 openModal를 false로 변경해줌.
openModal state가 true일때만 모달을 보여줌
간단한 모달 실행 gif
만들고나서 보니까
closeModal 컴포넌트와 Modal로 보여줄 요소를 따로 분리하지않고 modalPortal 컴포넌트 안에 작성해서 한 파일 내에서 모든걸 구현할 수도 있을꺼 같음
기능 별로 컴포넌트로 나눠야 마음이 편안해서 이렇게 했는데 만들고 보니 굳이 그럴필요까진 없어보이긴함...😥 규모가 커지면 나누는게 더 좋을듯!