티스토리 뷰
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 호출이 많은 애플리케이션에서 사용자 경험을 크게 향상시킬 수 있습니다. 사용자는 작업이 진행 중인지, 성공했는지, 실패했는지 명확하게 알 수 있기 때문입니다.
실제 프로젝트에서는 필요에 따라 토스트의 위치, 지속 시간, 애니메이션 등을 조정하여 사용자 경험을 더 개선할 수 있습니다.
'react' 카테고리의 다른 글
무한스크롤 웹 접근성 챙기기 (1) | 2023.11.21 |
---|---|
React 다형성 컴포넌트 만들기 (범용성 높은 컴포넌트, Polymorphic, Typescript,Styled-Components), (ReactElement, ReactNode 에러) (1) | 2023.11.01 |
Eslint Import/Order (import 순서) 설정하기 (2) | 2023.05.12 |
CRA(create-react-app)로 만든 프로젝트 / Storybook 7 버전에서 절대 경로를 설정하는 방법 (with Typescript) / jest 절대 경로 설정하기 (0) | 2023.05.04 |
React에서 ApolloGraphQL을 사용한 graphQL 사용 방법 (5) | 2023.04.17 |
- Total
- Today
- Yesterday
- electron
- 프리온보딩
- NextApiRequest
- React
- import/order
- nextjs
- 노마드코더
- NextRequest
- 위코드
- WSL2
- C언어
- TopLayer
- error
- 북클럽
- env
- 초보
- jest
- Storybook
- createPortal
- 프론트앤드
- CLASS
- 원티드
- 우아한테크코스
- 윤성우 열혈C프로그래밍
- 스토리 북
- javascript
- 아차산
- nodejs
- 노개북
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |