TIL #67 | [Next.js] Next.js 13에서 커스텀 모달 구현하기

kibi·2024년 1월 21일
0

TIL (Today I Learned)

목록 보기
67/83

Next.js 13버전에서 portal을 사용하여 커스텀 모달(custom alert, custom confirm)을 만들어보았다.

1. layout.tsx

import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "develofarm",
  description: "Find a project",
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {children}
        <div id="portal" className="z-100"></div>
      </body>
    </html>
  )
}

2. 커스텀 모달 레이아웃 및 핸들러 구성하기

"use client"

import React, { useEffect } from "react"
import ReactDOM from "react-dom"
import useCustomModalStore from "@/store/customModal"
import Button from "./ui/Button"

const CustomModal = () => {
  const {
    modalMessage,
    modalType,
    viewCustomModal,
    setViewCustomModal,
    handler,
  } = useCustomModalStore((state) => state)

  /** 뒷배경 스크롤 방지 */
  useEffect(() => {
    if (viewCustomModal) {
      document.body.style.cssText = `
        position: fixed;
        top: -${window.scrollY}px;
        overflow-y: scroll;
        width: 100%;`
      return () => {
        const scrollY = document.body.style.top
        document.body.style.cssText = ""
        window.scrollTo(0, parseInt(scrollY || "0", 10) * -1)
      }
    }
  }, [viewCustomModal])

  /** 확인 핸들러 */
  const confirmHandler = (e: React.MouseEvent<HTMLButtonElement>) => {
    if (e.target === e.currentTarget) {
      setViewCustomModal(false)

      if (!handler) return console.log("지정된 함수가 없습니다.")
      handler()
    }
  }

  /** 취소 핸들러 */
  const cancelHandler = (e: React.MouseEvent<HTMLButtonElement>) => {
    if (e.target === e.currentTarget) {
      setViewCustomModal(false)
    }
  }

  if (!viewCustomModal) return null

  return ReactDOM.createPortal(
    <>
      <div className="fixed top-0 left-0 right-0 bottom-0 flex bg-black bg-opacity-50 z-100" />
      <div className="fixed top-[50%] left-[50%] flex transform: translate-x-[-50%] translate-y-[-50%] p-[50px] w-[450px] h-[260px] bg-white z-200 rounded-3xl">
        <div className="w-full h-full flex flex-col justify-around">
          <h3 className="text-[20px] font-[600] whitespace-pre-line text-center">
            {modalMessage}
          </h3>
          <section className="w-full flex justify-around px-8">
            {modalType === "confirm" && (
              <Button
                handler={cancelHandler}
                text="취소"
                type="border"
                color="#297A5F"
              />
            )}
            <Button handler={confirmHandler} text="확인" color="#297A5F" />
          </section>
        </div>
      </div>
    </>,
    document.getElementById("portal") as HTMLElement,
  )
}

export default CustomModal
  1. useCustomModal hook 만들기
import useCustomModalStore from "@/store/customModal"

export const useCustomModal = () => {
  const { setModalMessage, setModalType, setViewCustomModal, setHandler } =
    useCustomModalStore((state) => state)

  const openCustomModalHandler = (
    message: string,
    type: string,
    handler?: () => void,
  ) => {
    setViewCustomModal(true)
    setModalMessage(message)
    setModalType(type)
    handler && setHandler(handler)
  }

  return { openCustomModalHandler }
}

4. hook을 사용하여 커스텀 모달 사용하기

1) alert

openCustomModalHandler("검색되었습니다.", "alert")

2) confirm

const onClickMemberCardHandler = () => {
  const handler = () => {
    router.push("/signin")
  }

  if (!currentUserId) {
    openCustomModalHandler(
      `로그인이 필요합니다.
로그인 페이지로 이동하시겠습니까?`,
      "confirm",
      handler,
    )
  } else {
    setSelectedMember(user as ExtendedUsersType)
    setViewMemberModal(true)
  }
}
  ```

0개의 댓글