thumbnail

portal 을 활용한 단일 next.js toast 만들기

생성일2023. 4. 11.
태그
작성자지한솔

안녕하세요!
 
오늘은 React Portal을 활용해서 블로그에 toast 메세지를 만들어 볼 계획입니다. 혹시나 잘못한 구조나, 이상한 코드가 발견된다면 언제든지 댓글 남겨주시면 감사드리겠습니다!
 
우선 제가 처음 생각했던 구조는 이러합니다.
Toast Class 생성 → render 를 통해 Toast를 추가 → 시간 경과 후 Toast array에서 시간 순으로 삭제 → render
의 구조를 잡았으나.. 아니 왜 render를 지원하지 않는거지.. 이전에 한번 만들었던 기억이 있는데, 대충 이런 구조로 개발 했던게 생각나서 찾아보니까 이제 render를 사용하지 않는다고 하네요.. 그럼 이제 어떻게 해야하지 하다가 우선.. portal로 구현하고, 추후에 더 괜찮은 방법이 있다면 옮겨가도록 하자! 로 우선 마음을 잡았습니다.

portal로 간단한 toast 컴포넌트 만들어보기.

지금 우리의 예제는 항상 next13 버전이고, ts 환경인 것을 기억해주길 바랍니다.. 우선 저는 layout.tsx에서 portal을 사용할 toast-root 를 하나 생성하였습니다.
 
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <div id="toast-root" className="fixed top-24 right-2/4 transform translate-x-2/4 flex flex-col z-30 w-64 space-y-2" /> {children} </body> </html> ); }
 
우리는 toast-root에 추가해나갈 예정입니다. 우선 css는 tailwind를 사용했고, 대충 요약하자면 fixed로 화면 중앙에 두겠다는 뜻입니다. 이제 toast component를 작성해보도록 하겠습니다.
 
import React, { useEffect } from "react"; import ReactDOM from "react-dom"; import style from "./toast.module.css"; interface ToastProps { message: string; } function Toast({ message }: ToastProps) { return ReactDOM.createPortal( <div className={ "opacity-0 visited:opacity-100 w-full py-3 flex bg-indigo-300 rounded-lg justify-center " + style.toast } > <div className="text-sm font-bold">{message}</div> </div>, document.getElementById("toast-root")! ); } export default Toast;
 
document.getElementById를 통해 아까 생성한 toast-root에 portal을 새로 생성합니다. toast 컴포넌트는 message(전달 내용)과 onClose(종료) 를 인자값으로 받습니다.
 
여기서, 기존의 toast message라면 여러개가 동시에 뜨겠지만, 우리는 toast message를 하나만 띄울 겁니다. 왜냐면.. 라이브러리는 커스텀하기 싫고.. 그렇다고 toast를 만들자니 이전에 만들었던 render 방식이 이제 지원을 안해서.. 추후에 만들어서 변경해두도록 하겠습니다..
 
그럼 이제 이 portal 을 불러서 사용하도록 하겠습니다. 우선 우리는 하나만 뜨는거기 때문에 toast 하나가 떠있을때에는 다른 toast가 뜨지 못하도록 구현하도록 하겠습니다.
 
"use client"; import React, { useState } from "react"; import { IoShareSocialSharp } from "react-icons/io5"; export default function Header({ post }: Props) { const [time, setTime] = useState<ReturnType<typeof setTimeout>>(); const copyUrl = () => { if (time) return; const timeOut = setTimeout(() => { setTime(undefined); }, 3000); setTime(timeOut); }; return ( <div className="flex flex-col py-7 border-y"> <IoShareSocialSharp onClick={toast} /> {time && <Toast message="클립보드에 저장하였습니다." />} </div> ); }
 
하나만 사용하니까.. setTimeout을 저장해둘 state를 만들어두고, 그 state가 활성화 될때 visible 되게 작성하였습니다. 이렇게 하면 중복 처리를 할 수 있습니다.
 
일단 간단하게 하나만 나오게 작업을 했는데.. 사실 이게 최종 목표는 아니라 다시 이전의 방식으로 toast message를 표현할 수 있게 변경해볼 생각입니다. 이번에는 대충 portal 을 이렇게 사용한다 정도로만 포스팅을 하려고 작성한거라..
 
다음에는 더 발전된 toast message를 들고오겠습니다~!!