Eun_Frontend
  • [HTML] <dialog> 태그 + createPortal로 모달(modal) 만들기?? [10/3 study]
    2024년 10월 03일 14시 41분 34초에 업로드 된 글입니다.
    작성자: 동혁이

     

     

    <dialog> 태그로 모달(modal) 만들기??

     

     

     

     

    이 글을 작성하게 된 이유

    원래는 실제 프로젝트에서 useModal 커스텀훅과 상위 파일인 layout.tsx에 div태그에 id값을 주어야 하는 방법까지 번거롭게 작업했었다.

    하지만 강의를 들으면서 상위 layout 파일에 div 요소도 안넣고 더 간편하게 사용하는 방법을 알게 되어 글을 작성해본다.

    import '../styles/globals.css';
    
    import Providers from './providers';
    
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="ko">
          <body>
            <Providers>{children}</Providers>
            <div id="modal-root" />
          </body>
        </html>
      );
    }
    'use client';
    
    import { type ReactNode } from 'react';
    import ReactDOM from 'react-dom';
    
    import { useModal } from '@/_hooks/useModal';
    
    type ModalProps = {
      children: ReactNode;
      isOpen: boolean;
      onClose: () => void;
    };
    
    /**
     * 사용법
     * ex)
     * const { isOpen, openModal, closeModal } = useModal();
     * 
     *  <button onClick={openModal}>Open Modal</button>
        <Modal isOpen={isOpen} onClose={closeModal}>
          <div className="m-auto px-[90px] pb-[28px] pt-[26px] text-right text-[18px] md:w-[540px] md:px-[33px]">
            <p className="pb-[43px] pt-[53px] text-center">가입이 완료되었습니다!</p>
            <span className="flex justify-center md:justify-end">
              <button className="h-[42px] w-[138px] rounded-[8px] bg-black text-white">확인</button> // 버튼 컴포넌트 넣어주시면 될 것 같습니다!
              <button onClick={closeModal}>취소</button> // 버튼 컴포넌트 넣어주시면 될 것 같습니다!
            </span>
          </div>
        </Modal>
     */
    
    /**
     * Modal 컴포넌트의 props
     *
     * @property {boolean} isOpen - Modal Open 여부
     * @property {() => void} onClose - Modal Close 함수
     * @property {ReactNode} children - Modal 자식 요소들
     */
    
    export default function Modal({ isOpen, onClose, children }: ModalProps) {
      const { isMounted } = useModal();
    
      const handleBackgroundClick = (event: React.MouseEvent<HTMLDivElement>) => {
        // 배경 클릭 시 모달 닫기
        if (event.target === event.currentTarget) {
          onClose();
        }
      };
    
      const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
        if (event.key === 'Escape') {
          onClose();
        }
      };
    
      if (!isOpen || !isMounted) return null;
    
      return ReactDOM.createPortal(
        <div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-70" onClick={handleBackgroundClick} onKeyDown={handleKeyDown}>
          <div className="relative rounded-[12px] bg-white shadow-lg">{children}</div>
        </div>,
        document.getElementById('modal-root') as HTMLElement,
      );
    }

     

    바로바로 dialog 태그와 createPortal를 이용하면 된다!

    딱봐도 코드가 더 간결해지고 children을 받으면서 쉽게 커스텀도 용이해졌다!

    지금이라도 내 게시글을 보는 사람들은 이 방법으로 바꿨으면 좋겠다! (더 좋은 방법이 있으면 말좀요...! ㅎ)

    import { createPortal } from "react-dom";
    import React, { useRef, useState } from "react";
    
    type ModalProps = {
      isOpen: boolean;
      onClose: () => void;
      children: React.ReactNode;
    };
    
      const ModalComp = (
        <dialog
          ref={ref}
          open={isOpen}
          id={"modal"}
          className={`absolute left-0 top-0 w-screen h-screen bg-opacity-20 bg-gray-800 ${animation}`}
          onClick={(e) => {
            if (e.target === ref.current) {
              handleClose();
            }
          }}
        >
          <article
            id={"modal-content"}
            className={"fixed left-1/4 top-1/4 w-1/2 h-1/2 bg-white rounded-lg p-5"}
          >
            {children}
          </article>
        </dialog>
      );
    
      return createPortal(ModalComp, document.body);
    }
    댓글