오늘의 개발 23.08.08 - next13 모달 생성

EVELO·2023년 8월 8일
0

오늘의 개발

목록 보기
3/5

createPortal을 사용해서 모달 만들기

index.html에 dom요소들과 같은 위치에 생성하고 해당 모달 요소의 z-index를 통해서 뒤에 배경을 가리고 모달을 보여주는식으로 접근.

1. next.js에서는 index.html이 없기때문에 요소를 감싸고있는 layout 파일에서 생성

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>

2. 모달 portal 생성

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에 요소로 전달

3. 모달의 자식요소와 모달요소 사이에 모달을 닫는 기능을 해줄 컴포넌트 생성

모달의 내용 밖을 클릭하거나 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;

4. 모달로 보여줄 요소 파일 생성

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;

5. 지금까지 만든 요소들을 보여줄 페이지 생성

모달을 테스트하기위해 임의로 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 컴포넌트 안에 작성해서 한 파일 내에서 모든걸 구현할 수도 있을꺼 같음
기능 별로 컴포넌트로 나눠야 마음이 편안해서 이렇게 했는데 만들고 보니 굳이 그럴필요까진 없어보이긴함...😥 규모가 커지면 나누는게 더 좋을듯!

profile
스펀지가 되고싶은 개발자

0개의 댓글