diff --git a/src/features/menu/components/MenuPageContainer.tsx b/src/features/menu/components/MenuPageContainer.tsx index 75e1652..2437067 100644 --- a/src/features/menu/components/MenuPageContainer.tsx +++ b/src/features/menu/components/MenuPageContainer.tsx @@ -10,6 +10,7 @@ import { useCart } from "@/features/cart/hooks/useCart"; import OrderSummaryBar from "@/features/menu/components/bar/OrderSummaryBar"; import { ItemCount } from "@/features/menu/components/button/CartButton"; import CartToast from "@/features/menu/components/toast/CartToast"; +import { useToast } from "@/shared/hooks/useToast"; interface MenuPageContainerProps { hakgwanMenuData: HakgwanMenuData; @@ -26,8 +27,7 @@ export default function MenuPageContainer({ return 0; }); - const [toastVisible, setToastVisible] = useState(false); - const [toastMessage, setToastMessage] = useState(""); + const { visible: toastVisible, message: toastMessage, showToast } = useToast(3000); const { cartInfo, addToCart } = useCart(); @@ -47,18 +47,10 @@ export default function MenuPageContainer({ const handleAddToCart = (menuId: number): void => { addToCart(menuId, { onSuccess: () => { - setToastMessage("장바구니에 쏙 담았어요!"); - setToastVisible(true); - setTimeout(() => { - setToastVisible(false); - }, 3000); + showToast("장바구니에 쏙 담았어요!"); }, onError: (message) => { - setToastMessage(message); - setToastVisible(true); - setTimeout(() => { - setToastVisible(false); - }, 3000); + showToast(message); } }); }; diff --git a/src/shared/hooks/useToast.ts b/src/shared/hooks/useToast.ts new file mode 100644 index 0000000..89c43b0 --- /dev/null +++ b/src/shared/hooks/useToast.ts @@ -0,0 +1,83 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; + +interface UseToastReturn { + visible: boolean; + message: string; + showToast: (message: string) => void; +} + +/** + * 토스트 메시지 관리 훅 + * - 타이머 충돌 방지 + * - 기존 토스트가 있으면 새로운 토스트로 교체 + */ +export function useToast( + duration: number = 3000, + animationDuration: number = 200, +): UseToastReturn { + const [visible, setVisible] = useState(false); + const [message, setMessage] = useState(""); + const timerRef = useRef(null); + const hideTimerRef = useRef(null); + const visibleRef = useRef(false); + + const showToast = useCallback((message: string) => { + // 기존 타이머 정리 + if (timerRef.current) { + window.clearTimeout(timerRef.current); + } + if (hideTimerRef.current) { + window.clearTimeout(hideTimerRef.current); + } + + // 이미 토스트가 표시중이면 먼저 제거 + if (visibleRef.current) { + // 기존 토스트 숨김 + setVisible(false); + visibleRef.current = false; + + // 애니메이션 완료 대기 + hideTimerRef.current = window.setTimeout(() => { + setMessage(message); + setVisible(true); + visibleRef.current = true; + + // duration 후 토스트 숨김 + timerRef.current = window.setTimeout(() => { + setVisible(false); + visibleRef.current = false; + timerRef.current = null; + }, duration); + + hideTimerRef.current = null; + }, animationDuration); + } else { + // 기존 토스트가 없는 경우 + setMessage(message); + setVisible(true); + visibleRef.current = true; + + timerRef.current = window.setTimeout(() => { + setVisible(false); + visibleRef.current = false; + timerRef.current = null; + }, duration); + } + }, [duration, animationDuration]); + + // 컴포넌트 언마운트 시 타이머 정리 + useEffect(() => { + return () => { + if (timerRef.current) { + window.clearTimeout(timerRef.current); + } + if (hideTimerRef.current) { + window.clearTimeout(hideTimerRef.current); + } + }; + }, []); + + return { visible, message, showToast }; +} \ No newline at end of file