CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sonner

An opinionated toast component for React providing comprehensive toast notifications with customization options

Pending
Overview
Eval results
Files

react-hooks.mddocs/

React Hooks

React hooks for integrating with toast state and managing toast notifications programmatically within React components.

Capabilities

useSonner Hook

React hook for accessing the current toast state, useful for building custom toast-aware components or debugging.

/**
 * React hook that provides access to current toast state
 * @returns Object containing array of active toasts
 */
function useSonner(): {
  toasts: ToastT[];
};

interface ToastT {
  id: number | string;
  title?: (() => React.ReactNode) | React.ReactNode;
  type?: "normal" | "action" | "success" | "info" | "warning" | "error" | "loading" | "default";
  icon?: React.ReactNode;
  jsx?: React.ReactNode;
  richColors?: boolean;
  invert?: boolean;
  closeButton?: boolean;
  dismissible?: boolean;
  description?: (() => React.ReactNode) | React.ReactNode;
  duration?: number;
  delete?: boolean;
  action?: Action | React.ReactNode;
  cancel?: Action | React.ReactNode;
  onDismiss?: (toast: ToastT) => void;
  onAutoClose?: (toast: ToastT) => void;
  promise?: Promise<any> | (() => Promise<any>);
  cancelButtonStyle?: React.CSSProperties;
  actionButtonStyle?: React.CSSProperties;
  style?: React.CSSProperties;
  unstyled?: boolean;
  className?: string;
  classNames?: ToastClassnames;
  descriptionClassName?: string;
  position?: Position;
}

Usage Examples

Basic Toast State Access

import React from "react";
import { useSonner, toast } from "sonner";

function ToastStatus() {
  const { toasts } = useSonner();
  
  return (
    <div>
      <p>Active toasts: {toasts.length}</p>
      <button onClick={() => toast("New toast!")}>
        Add Toast
      </button>
      <button onClick={() => toast.dismiss()}>
        Clear All
      </button>
    </div>
  );
}

Toast Type Counter

import React from "react";
import { useSonner, toast } from "sonner";

function ToastAnalytics() {
  const { toasts } = useSonner();
  
  const toastCounts = React.useMemo(() => {
    return toasts.reduce((counts, toast) => {
      const type = toast.type || "default";
      counts[type] = (counts[type] || 0) + 1;
      return counts;
    }, {});
  }, [toasts]);
  
  return (
    <div className="toast-analytics">
      <h3>Current Toast Status</h3>
      {Object.entries(toastCounts).map(([type, count]) => (
        <div key={type}>
          {type}: {count}
        </div>
      ))}
      
      <div className="controls">
        <button onClick={() => toast.success("Success!")}>
          Add Success
        </button>
        <button onClick={() => toast.error("Error!")}>
          Add Error
        </button>
        <button onClick={() => toast.loading("Loading...")}>
          Add Loading
        </button>
      </div>
    </div>
  );
}

Loading State Indicator

import React from "react";
import { useSonner } from "sonner";

function LoadingIndicator() {
  const { toasts } = useSonner();
  
  const hasLoadingToasts = toasts.some(toast => toast.type === "loading");
  const loadingCount = toasts.filter(toast => toast.type === "loading").length;
  
  if (!hasLoadingToasts) {
    return null;
  }
  
  return (
    <div className="loading-indicator">
      <div className="spinner" />
      <span>
        {loadingCount === 1 
          ? "Processing..." 
          : `${loadingCount} operations in progress...`
        }
      </span>
    </div>
  );
}

Custom Toast Manager

import React from "react";
import { useSonner, toast } from "sonner";

function ToastManager() {
  const { toasts } = useSonner();
  
  const dismissToast = (id: string | number) => {
    toast.dismiss(id);
  };
  
  const dismissByType = (type: string) => {
    toasts
      .filter(t => t.type === type)
      .forEach(t => toast.dismiss(t.id));
  };
  
  const dismissOlderThan = (minutes: number) => {
    const cutoff = Date.now() - (minutes * 60 * 1000);
    toasts
      .filter(t => {
        // Approximate age based on ID if it's a timestamp
        const toastTime = typeof t.id === 'number' ? t.id : Date.now();
        return toastTime < cutoff;
      })
      .forEach(t => toast.dismiss(t.id));
  };
  
  return (
    <div className="toast-manager">
      <h3>Toast Manager ({toasts.length} active)</h3>
      
      <div className="toast-list">
        {toasts.map(toast => (
          <div key={toast.id} className="toast-item">
            <span className={`toast-type ${toast.type}`}>
              {toast.type || "default"}
            </span>
            <span className="toast-title">
              {typeof toast.title === "function" ? toast.title() : toast.title}
            </span>
            <button onClick={() => dismissToast(toast.id)}>
              ×
            </button>
          </div>
        ))}
      </div>
      
      <div className="controls">
        <button onClick={() => dismissByType("error")}>
          Clear Errors
        </button>
        <button onClick={() => dismissByType("success")}>
          Clear Success
        </button>
        <button onClick={() => dismissOlderThan(5)}>
          Clear Old (5+ min)
        </button>
        <button onClick={() => toast.dismiss()}>
          Clear All
        </button>
      </div>
    </div>
  );
}

Toast Persistence Monitor

import React from "react";
import { useSonner } from "sonner";

function ToastPersistenceMonitor() {
  const { toasts } = useSonner();
  const [persistentToasts, setPersistentToasts] = React.useState([]);
  
  React.useEffect(() => {
    // Track toasts that have been around for a long time
    const persistent = toasts.filter(toast => {
      // Check if duration is Infinity or very long
      return toast.duration === Infinity || 
             (toast.duration && toast.duration > 30000) ||
             toast.type === "loading";
    });
    
    setPersistentToasts(persistent);
  }, [toasts]);
  
  if (persistentToasts.length === 0) {
    return null;
  }
  
  return (
    <div className="persistence-monitor">
      <h4>⚠️ Persistent Toasts ({persistentToasts.length})</h4>
      <p>These toasts won't auto-dismiss:</p>
      <ul>
        {persistentToasts.map(toast => (
          <li key={toast.id}>
            {toast.type}: {typeof toast.title === "function" ? toast.title() : toast.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

Performance Monitor

import React from "react";
import { useSonner } from "sonner";

function ToastPerformanceMonitor() {
  const { toasts } = useSonner();
  const [maxToasts, setMaxToasts] = React.useState(0);
  const [toastHistory, setToastHistory] = React.useState([]);
  
  React.useEffect(() => {
    setMaxToasts(Math.max(maxToasts, toasts.length));
    
    // Keep a history of toast count changes
    setToastHistory(prev => [
      ...prev.slice(-20), // Keep last 20 entries
      { timestamp: Date.now(), count: toasts.length }
    ]);
  }, [toasts.length]);
  
  const averageToasts = React.useMemo(() => {
    if (toastHistory.length === 0) return 0;
    const sum = toastHistory.reduce((acc, entry) => acc + entry.count, 0);
    return (sum / toastHistory.length).toFixed(1);
  }, [toastHistory]);
  
  return (
    <div className="performance-monitor">
      <h4>Toast Performance</h4>
      <div className="metrics">
        <div>Current: {toasts.length}</div>
        <div>Peak: {maxToasts}</div>
        <div>Average: {averageToasts}</div>
      </div>
      
      {toasts.length > 5 && (
        <div className="warning">
          ⚠️ High toast count may impact performance
        </div>
      )}
    </div>
  );
}

Conditional Rendering Based on Toasts

import React from "react";
import { useSonner } from "sonner";

function AppWithToastAwareness() {
  const { toasts } = useSonner();
  
  const hasErrors = toasts.some(t => t.type === "error");
  const hasLoading = toasts.some(t => t.type === "loading");
  const hasSuccessToasts = toasts.some(t => t.type === "success");
  
  return (
    <div className={`app ${hasErrors ? "has-errors" : ""}`}>
      <header>
        <h1>My App</h1>
        {hasLoading && <div className="loading-indicator">Processing...</div>}
      </header>
      
      <main>
        {hasErrors && (
          <div className="error-state">
            <p>Some operations failed. Check notifications for details.</p>
          </div>
        )}
        
        {hasSuccessToasts && (
          <div className="success-state">
            <p>✅ Recent successful operations</p>
          </div>
        )}
        
        <YourAppContent />
      </main>
      
      {toasts.length > 0 && (
        <div className="toast-summary">
          {toasts.length} active notification{toasts.length !== 1 ? "s" : ""}
        </div>
      )}
    </div>
  );
}

Advanced Patterns

Custom Toast Provider

import React from "react";
import { useSonner, toast } from "sonner";

const ToastContext = React.createContext({
  toasts: [],
  addToast: (message, options) => {},
  clearToasts: () => {},
  hasToasts: false,
});

export function ToastProvider({ children }) {
  const { toasts } = useSonner();
  
  const contextValue = React.useMemo(() => ({
    toasts,
    addToast: (message, options) => toast(message, options),
    clearToasts: () => toast.dismiss(),
    hasToasts: toasts.length > 0,
  }), [toasts]);
  
  return (
    <ToastContext.Provider value={contextValue}>
      {children}
    </ToastContext.Provider>
  );
}

export function useToastContext() {
  return React.useContext(ToastContext);
}

Toast State Synchronization

import React from "react";
import { useSonner } from "sonner";

function useToastSync() {
  const { toasts } = useSonner();
  
  // Sync toast state to localStorage
  React.useEffect(() => {
    const toastData = toasts.map(t => ({
      id: t.id,
      type: t.type,
      title: typeof t.title === "string" ? t.title : "[React Element]",
      timestamp: Date.now()
    }));
    
    localStorage.setItem("recent-toasts", JSON.stringify(toastData));
  }, [toasts]);
  
  // Analytics tracking
  React.useEffect(() => {
    if (typeof window !== "undefined" && window.gtag) {
      toasts.forEach(toast => {
        if (toast.type === "error") {
          window.gtag("event", "toast_error", {
            error_type: toast.type,
            error_message: toast.title
          });
        }
      });
    }
  }, [toasts]);
}

Common Use Cases

  1. Debug Panel: Display current toast state during development
  2. Analytics: Track toast usage patterns and error rates
  3. Performance Monitoring: Watch for excessive toast creation
  4. Conditional UI: Show/hide elements based on toast presence
  5. State Synchronization: Sync toast state with external systems
  6. Custom Management: Build advanced toast management interfaces

The useSonner hook provides real-time access to the current toast state, enabling these advanced patterns while maintaining performance through React's built-in optimization mechanisms.

Install with Tessl CLI

npx tessl i tessl/npm-sonner

docs

advanced-toast-features.md

core-toast-functions.md

index.md

react-hooks.md

toaster-component.md

tile.json