typescriptintermediate
Toast Notification System
Build a toast notification system with auto-dismiss, stacking, animations, and severity levels.
typescriptPress ⌘/Ctrl + Shift + C to copy
import { createContext, useCallback, useContext, useState, useRef, useEffect } from 'react';
type ToastType = 'success' | 'error' | 'warning' | 'info';
interface Toast {
id: string;
message: string;
type: ToastType;
duration: number;
}
interface ToastContextType {
toast: (message: string, type?: ToastType, duration?: number) => void;
}
const ToastContext = createContext<ToastContextType | null>(null);
const typeStyles: Record<ToastType, string> = {
success: 'bg-green-900/90 border-green-500/50 text-green-200',
error: 'bg-red-900/90 border-red-500/50 text-red-200',
warning: 'bg-yellow-900/90 border-yellow-500/50 text-yellow-200',
info: 'bg-blue-900/90 border-blue-500/50 text-blue-200',
};
const icons: Record<ToastType, string> = {
success: '\u2713', error: '\u2717', warning: '\u26A0', info: '\u2139',
};
function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const timers = useRef(new Map<string, NodeJS.Timeout>());
const removeToast = useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
const timer = timers.current.get(id);
if (timer) { clearTimeout(timer); timers.current.delete(id); }
}, []);
const toast = useCallback((message: string, type: ToastType = 'info', duration = 4000) => {
const id = crypto.randomUUID();
setToasts((prev) => [...prev, { id, message, type, duration }]);
const timer = setTimeout(() => removeToast(id), duration);
timers.current.set(id, timer);
}, [removeToast]);
return (
<ToastContext.Provider value={{ toast }}>
{children}
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-sm">
{toasts.map((t) => (
<div
key={t.id}
className={`flex items-center gap-2 px-4 py-3 rounded-lg border shadow-lg
animate-[slideIn_0.3s_ease] ${typeStyles[t.type]}`}
role="alert"
>
<span className="text-lg">{icons[t.type]}</span>
<span className="flex-1 text-sm">{t.message}</span>
<button
onClick={() => removeToast(t.id)}
className="opacity-60 hover:opacity-100 text-lg leading-none"
>
×
</button>
</div>
))}
</div>
</ToastContext.Provider>
);
}
function useToast() {
const ctx = useContext(ToastContext);
if (!ctx) throw new Error('useToast must be within ToastProvider');
return ctx;
}
// Usage:
// <ToastProvider><App /></ToastProvider>
// const { toast } = useToast();
// toast('Saved!', 'success');
// toast('Network error', 'error', 6000);
export { ToastProvider, useToast };Use Cases
- User feedback notifications
- Form submission confirmations
- Error state communication
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptbeginner
Search with Text Highlighting
Build a search component that highlights matching text within results using safe HTML rendering.
Best for: Search results display
#react#search
typescriptintermediate
Error Boundary with Fallback UI
Class-based error boundary component that catches render errors and displays a customizable fallback UI.
Best for: Graceful error recovery
#error-boundary#error-handling
typescriptbeginner
usePrevious Hook
Track the previous value of any state or prop using a ref-based hook for comparison logic.
Best for: Detecting value changes
#hooks#state
typescriptintermediate
React Custom Hook for Data Fetching
A reusable useFetch hook with loading states, error handling, caching, and abort support.
Best for: Reusable data fetching across components
#react#hooks