React에서 react-toastify로 효과적인 알림 구현하기
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,
}
);
};
위 코드는 두 가지 유틸리티 함수를 제공합니다:
- showToast: 일반적인 토스트 메시지를 표시
- 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;
이 컴포넌트는:
- toast-root 요소를 찾아 상태로 저장합니다.
- ReactDOM.createPortal을 사용하여 ToastContainer를 해당 요소에 렌더링합니다.
- 클라이언트 사이드에서만 실행되도록 '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을 조합하면 깔끔하고 효과적인 알림 시스템을 쉽게 구현할 수 있습니다. 이 방식의 장점은:
- 관심사 분리: 토스트 관련 로직과 DOM 요소가 별도로 관리됩니다.
- 재사용성: 유틸리티 함수를 통해 어느 컴포넌트에서든 일관된 방식으로 토스트를 표시할 수 있습니다.
- 유연성: Promise 기반의 토스트를 통해 비동기 작업의 상태를 자연스럽게 표시할 수 있습니다.
특히 Promise 기반 토스트는 API 호출이 많은 애플리케이션에서 사용자 경험을 크게 향상시킬 수 있습니다. 사용자는 작업이 진행 중인지, 성공했는지, 실패했는지 명확하게 알 수 있기 때문입니다.
실제 프로젝트에서는 필요에 따라 토스트의 위치, 지속 시간, 애니메이션 등을 조정하여 사용자 경험을 더 개선할 수 있습니다.