react

React에서 react-toastify로 효과적인 알림 구현하기

YG - 96년생 , 강아지 있음, 개발자 희망 2025. 3. 11. 06:35

React에서 react-toastify로 효과적인 알림 구현하기

개발을 하다 보면 사용자에게 작업의 성공, 실패, 진행 상태 등을 알려줄 필요가 있습니다. 이런 알림(notification)을 구현하는 방법은 여러 가지가 있지만, React 환경에서는 react-toastify 라이브러리가 가장 인기 있고 사용하기 쉬운 선택지 중 하나입니다.

이 글에서는 react-toastify를 설치하고 효과적으로 사용하는 방법, 그리고 Portal을 활용해 더 나은 구조로 구현하는 방법까지 알아보겠습니다.

react-toastify란?

react-toastify는 React 애플리케이션에서 토스트 메시지(작은 알림 팝업)를 쉽게 구현할 수 있게 해주는 라이브러리입니다. npm에 따르면 주간 다운로드 수가 230만개 이상으로 인기있는 라이브러리입니다.

설치하기

npm을 사용하여 간단하게 설치할 수 있습니다:

npm install --save react-toastify

 

 

 

 

기본 설정 및 유틸리티 함수 만들기

먼저 토스트 메시지를 쉽게 호출할 수 있는 유틸리티 함수를 만들어 보겠습니다. toast.ts 파일을 생성하고 다음과 같이 작성합니다:

import { Bounce, toast } from 'react-toastify';

type TypeOptions = 'info' | 'success' | 'warning' | 'error' | 'default';

interface ShowPromiseToast {
  pending: string;
  success: string;
  error: string;
}

export const showToast = (message: string, type: TypeOptions = 'default') => {
  toast(message, {
    type,
    position: 'bottom-center',
    autoClose: 5000,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: true,
    draggable: true,
    progress: undefined,
    theme: 'light',
    transition: Bounce,
  });
};

export const showPromiseToast = (
  data: Promise<unknown>,
  { error, pending, success }: ShowPromiseToast
) => {
  return toast.promise(
    data,
    {
      pending,
      success,
      error,
    },
    {
      position: 'bottom-center',
      autoClose: 5000,
      hideProgressBar: false,
      closeOnClick: true,
      pauseOnHover: true,
      draggable: true,
      progress: undefined,
      theme: 'light',
      transition: Bounce,
    }
  );
};

 

위 코드는 두 가지 유틸리티 함수를 제공합니다:

  1. showToast: 일반적인 토스트 메시지를 표시
  2. showPromiseToast: Promise 기반 작업(API 호출 등)의 진행 상태를 토스트로 표시

 

 

Portal을 활용한 ToastContainer 구현

React에서 모달, 토스트 같은 UI 요소는 일반적으로 Portal을 사용하여 구현합니다. Portal을 사용하면 DOM 트리 상에서 부모-자식 관계와 상관없이 원하는 위치에 컴포넌트를 렌더링할 수 있습니다.

1. HTML에 토스트 컨테이너용 요소 추가하기

React(Vite 등) 환경에서:

<body>
  <div id="root"></div>
  <div id="toast-root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>

Next.js 환경에서 (pages/_document.tsx):

import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="ko">
      <Head />
      <body>
        <Main />
        <NextScript />
        {/* ✅ Toastify 전용 루트 추가 */}
        <div id="toast-root"></div>
      </body>
    </Html>
  );
}

2. Portal 컴포넌트 생성하기

 

components/PortalToastContainer.tsx 파일을 생성하고 다음과 같이 작성합니다:

'use client';

import { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { Bounce, ToastContainer } from 'react-toastify';

const PortalToastContainer = () => {
  const [toastRoot, setToastRoot] = useState<HTMLElement | null>(null);

  useEffect(() => {
    setToastRoot(document.getElementById('toast-root'));
  }, []);

  return toastRoot
    ? ReactDOM.createPortal(
        <ToastContainer
          position="bottom-center"
          autoClose={5000}
          hideProgressBar={false}
          newestOnTop
          closeOnClick
          rtl={false}
          pauseOnFocusLoss
          draggable
          pauseOnHover
          theme="light"
          transition={Bounce}
        />,
        toastRoot
      )
    : null;
};

export default PortalToastContainer;

 

이 컴포넌트는:

  1. toast-root 요소를 찾아 상태로 저장합니다.
  2. ReactDOM.createPortal을 사용하여 ToastContainer를 해당 요소에 렌더링합니다.
  3. 클라이언트 사이드에서만 실행되도록 'use client' 지시문을 포함합니다(Next.js App Router 사용 시).

 

3. 애플리케이션에 ToastContainer 추가하기

React 환경에서 (main.tsx):

<ChannelTalkProvider>
  <RouterProvider router={router} />
  <PortalToastContainer />
</ChannelTalkProvider>

Next.js 환경에서 (pages/_app.tsx):

<SWRConfig
  value={{
    fetcher: (url: string) =>
      fetch(url).then((response) => response.json()),
  }}
>
  <GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
    <Component {...pageProps} />
    <PortalToastContainer />
  </GoogleOAuthProvider>
</SWRConfig>

실제 사용 예시

이제 앞서 작성한 유틸리티 함수를 사용하여 토스트 메시지를 표시해 보겠습니다:

1. 일반 토스트 메시지

useEffect(() => {
  if (data?.ok) {
    router.push('/user/login');
    showToast('회원가입에 성공했습니다! 🎉');
  }
}, [data, router]);

2. Promise 기반 토스트 메시지 (API 호출 등)

showPromiseToast(
  result.then((res) => {
    if (!res.data?.createCoupon.ok) {
      throw new Error(
        res.data?.createCoupon.error || "쿠폰 생성에 실패했습니다.",
      );
    }

    navigate(ROUTES.ADMIN_COUPONS);

    return res; // 성공 시 반환값
  }),
  {
    success: "쿠폰 생성에 성공했습니다! 🎉",
    error: "쿠폰 생성에 실패했습니다 😢",
    pending: "쿠폰 생성 중입니다 ⏳",
  },
);

이 방식을 사용하면 Promise의 상태에 따라 자동으로 다른 메시지가 표시됩니다:

  • pending: Promise가 처리 중일 때
  • success: Promise가 성공적으로 완료되었을 때
  • error: Promise가 거부되었을 때

 

마무리

react-toastify와 React Portal을 조합하면 깔끔하고 효과적인 알림 시스템을 쉽게 구현할 수 있습니다. 이 방식의 장점은:

  1. 관심사 분리: 토스트 관련 로직과 DOM 요소가 별도로 관리됩니다.
  2. 재사용성: 유틸리티 함수를 통해 어느 컴포넌트에서든 일관된 방식으로 토스트를 표시할 수 있습니다.
  3. 유연성: Promise 기반의 토스트를 통해 비동기 작업의 상태를 자연스럽게 표시할 수 있습니다.

 

특히 Promise 기반 토스트는 API 호출이 많은 애플리케이션에서 사용자 경험을 크게 향상시킬 수 있습니다. 사용자는 작업이 진행 중인지, 성공했는지, 실패했는지 명확하게 알 수 있기 때문입니다.

 

실제 프로젝트에서는 필요에 따라 토스트의 위치, 지속 시간, 애니메이션 등을 조정하여 사용자 경험을 더 개선할 수 있습니다.