# Advanced Toast Features

Advanced toast functionality including promise integration, custom JSX content, action buttons, and state management utilities for complex notification scenarios.

## Capabilities

### Promise Toast Integration

Automatically manages toast states for asynchronous operations with loading, success, and error states.

```typescript { .api }
/**
 * Creates a toast that tracks a promise's lifecycle
 * @param promise - Promise or function that returns a promise
 * @param options - Configuration for different promise states
 * @returns Object with unwrap method and toast ID
 */
function toast.promise<T>(
  promise: Promise<T> | (() => Promise<T>),
  options: PromiseData<T>
): { unwrap(): Promise<T> } & (string | number);

interface PromiseData<ToastData = any> {
  /** Message or component to show during loading state */
  loading?: string | React.ReactNode;
  /** Message, component, or function to show on success */
  success?: string | React.ReactNode | ((data: ToastData) => React.ReactNode | string | Promise<React.ReactNode | string>);
  /** Message, component, or function to show on error */
  error?: string | React.ReactNode | ((error: any) => React.ReactNode | string | Promise<React.ReactNode | string>);
  /** Description that can be dynamic based on result */
  description?: string | React.ReactNode | ((data: any) => React.ReactNode | string | Promise<React.ReactNode | string>);
  /** Callback executed regardless of promise outcome */
  finally?: () => void | Promise<void>;
}
```

**Usage Examples:**

```typescript
import { toast } from "sonner";

// Basic promise toast
const uploadPromise = uploadFile(file);

toast.promise(uploadPromise, {
  loading: "Uploading file...",
  success: "File uploaded successfully!",
  error: "Failed to upload file"
});

// Dynamic success message
toast.promise(saveUserProfile(userData), {
  loading: "Saving profile...",
  success: (data) => `Profile saved! User ID: ${data.id}`,
  error: (error) => `Save failed: ${error.message}`
});

// With description and finally callback
toast.promise(processData(), {
  loading: "Processing your data...",
  success: "Data processed successfully",
  error: "Processing failed",
  description: (result) => `Processed ${result.count} items`,
  finally: () => {
    console.log("Processing completed");
    refreshUI();
  }
});

// Function-based promise
toast.promise(
  () => fetch("/api/data").then(res => res.json()),
  {
    loading: "Fetching data...",
    success: "Data loaded!",
    error: "Failed to load data"
  }
);

// Using unwrap to handle the original promise
const promiseToast = toast.promise(asyncOperation(), {
  loading: "Working...",
  success: "Done!",
  error: "Failed!"
});

// Access the original promise result
try {
  const result = await promiseToast.unwrap();
  console.log("Operation result:", result);
} catch (error) {
  console.error("Operation failed:", error);
}
```

### Custom JSX Toasts

Create completely custom toast content using React components.

```typescript { .api }
/**
 * Creates a toast with custom JSX content
 * @param jsx - Function that receives toast ID and returns React element
 * @param options - Additional configuration options
 * @returns Unique identifier for the created toast
 */
function toast.custom(
  jsx: (id: string | number) => React.ReactElement,
  options?: ExternalToast
): string | number;
```

**Usage Examples:**

```typescript
import { toast } from "sonner";

// Simple custom toast
toast.custom((t) => (
  <div style={{ padding: "12px" }}>
    <h3>Custom Toast</h3>
    <p>This is completely custom content!</p>
    <button onClick={() => toast.dismiss(t)}>
      Close
    </button>
  </div>
));

// Complex custom toast with actions
toast.custom((t) => (
  <div className="custom-toast">
    <div className="toast-header">
      <strong>New Message</strong>
      <small>2 minutes ago</small>
    </div>
    <div className="toast-body">
      <p>You have received a new message from John Doe.</p>
    </div>
    <div className="toast-actions">
      <button 
        onClick={() => {
          viewMessage();
          toast.dismiss(t);
        }}
        className="btn-primary"
      >
        View
      </button>
      <button 
        onClick={() => toast.dismiss(t)}
        className="btn-secondary"
      >
        Dismiss
      </button>
    </div>
  </div>
));

// Custom toast with form
toast.custom((t) => {
  const [email, setEmail] = React.useState("");
  
  const handleSubmit = (e) => {
    e.preventDefault();
    subscribeToNewsletter(email);
    toast.dismiss(t);
    toast.success("Subscribed successfully!");
  };
  
  return (
    <form onSubmit={handleSubmit} className="newsletter-toast">
      <h4>Subscribe to Newsletter</h4>
      <input
        type="email"
        placeholder="Enter your email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      <div className="form-actions">
        <button type="submit">Subscribe</button>
        <button type="button" onClick={() => toast.dismiss(t)}>
          Cancel
        </button>
      </div>
    </form>
  );
});

// Custom toast with options
toast.custom(
  (t) => <CustomNotificationComponent id={t} />,
  {
    duration: Infinity, // Keep open until manually dismissed
    position: "top-center"
  }
);
```

### Toast State Management

Functions for managing toast state and retrieving toast information.

```typescript { .api }
/**
 * Gets all toasts in history (including dismissed ones)
 * @returns Array of all toast objects
 */
function toast.getHistory(): ToastT[];

/**
 * Gets currently active (visible) toasts
 * @returns Array of active toast objects
 */
function toast.getToasts(): ToastT[];
```

**Usage Examples:**

```typescript
import { toast } from "sonner";

// Check current toast count
const activeToasts = toast.getToasts();
console.log(`Currently showing ${activeToasts.length} toasts`);

// Conditional toast creation
if (toast.getToasts().length < 3) {
  toast("New notification");
} else {
  console.log("Too many toasts, skipping...");
}

// Get toast history for analytics
const allToasts = toast.getHistory();
const errorToasts = allToasts.filter(t => t.type === "error");
console.log(`User saw ${errorToasts.length} error messages`);

// Find specific toast
const loadingToasts = toast.getToasts().filter(t => t.type === "loading");
if (loadingToasts.length > 0) {
  console.log("Operations still in progress");
}
```

### Toast Updates and Management

Update existing toasts or manage multiple related toasts.

```typescript { .api }
// Update existing toast by providing same ID
const toastId = toast("Initial message", { duration: 10000 });

// Later, update the same toast
toast("Updated message", { id: toastId });

// Update with different type
toast.success("Completed!", { id: toastId });
```

**Usage Examples:**

```typescript
import { toast } from "sonner";

// Progress tracking
const progressId = toast.loading("Starting process...");

setTimeout(() => {
  toast.loading("Step 1 of 3: Downloading...", { id: progressId });
}, 1000);

setTimeout(() => {
  toast.loading("Step 2 of 3: Processing...", { id: progressId });
}, 3000);

setTimeout(() => {
  toast.success("Process completed!", { id: progressId });
}, 5000);

// Multi-step form handling
let formToastId: string | number;

const handleFormStart = () => {
  formToastId = toast.loading("Validating form...");
};

const handleFormValidated = () => {
  toast.loading("Submitting data...", { id: formToastId });
};

const handleFormComplete = (result) => {
  if (result.success) {
    toast.success("Form submitted successfully!", { id: formToastId });
  } else {
    toast.error(`Submission failed: ${result.error}`, { id: formToastId });
  }
};

// Batch operations
const batchId = toast.loading("Processing 0/100 items...");

for (let i = 0; i < 100; i++) {
  await processItem(i);
  
  // Update progress every 10 items
  if (i % 10 === 0) {
    toast.loading(`Processing ${i}/100 items...`, { id: batchId });
  }
}

toast.success("All items processed!", { id: batchId });
```

## Advanced Patterns

### Promise Error Handling

```typescript
// Handle different HTTP error codes
toast.promise(apiCall(), {
  loading: "Saving...",
  success: "Saved successfully!",
  error: (error) => {
    if (error.status === 401) {
      return "Please log in to continue";
    } else if (error.status === 403) {
      return "You don't have permission to do this";
    } else if (error.status >= 500) {
      return "Server error. Please try again later";
    }
    return "Something went wrong";
  }
});

// Promise with retry functionality
const attemptOperation = async (retryCount = 0) => {
  try {
    const result = await riskyOperation();
    return result;
  } catch (error) {
    if (retryCount < 3) {
      throw error; // Let promise toast handle it
    }
    throw new Error("Max retries exceeded");
  }
};

toast.promise(attemptOperation(), {
  loading: "Attempting operation...",
  success: "Operation successful!",
  error: (error) => {
    return (
      <div>
        <p>Operation failed: {error.message}</p>
        <button onClick={() => toast.promise(attemptOperation(), { /* ... */ })}>
          Retry
        </button>
      </div>
    );
  }
});
```

### Complex Custom Components

```typescript
// Toast with image and rich content
const MediaToast = ({ id, media, onAction }) => (
  <div className="media-toast">
    <img src={media.thumbnail} alt="" className="media-thumbnail" />
    <div className="media-content">
      <h4>{media.title}</h4>
      <p>{media.description}</p>
      <div className="media-actions">
        <button onClick={() => onAction('play')}>Play</button>
        <button onClick={() => onAction('download')}>Download</button>
        <button onClick={() => toast.dismiss(id)}>×</button>
      </div>
    </div>
  </div>
);

// Usage
toast.custom((id) => (
  <MediaToast 
    id={id}
    media={mediaItem}
    onAction={(action) => handleMediaAction(action, mediaItem)}
  />
));

// Toast with live updates
const LiveUpdateToast = ({ id, initialData }) => {
  const [data, setData] = React.useState(initialData);
  
  React.useEffect(() => {
    const subscription = subscribeToUpdates((newData) => {
      setData(newData);
    });
    
    return () => subscription.unsubscribe();
  }, []);
  
  return (
    <div className="live-toast">
      <h4>Live Data: {data.value}</h4>
      <small>Last updated: {data.timestamp}</small>
      <button onClick={() => toast.dismiss(id)}>Close</button>
    </div>
  );
};
```

### Toast Orchestration

```typescript
// Sequential toasts
const showSequentialToasts = async () => {
  const step1 = toast.loading("Step 1: Preparing...");
  await delay(2000);
  
  toast.success("Step 1 complete", { id: step1 });
  await delay(1000);
  
  const step2 = toast.loading("Step 2: Processing...");
  await delay(3000);
  
  toast.success("Step 2 complete", { id: step2 });
  await delay(1000);
  
  toast.success("All steps completed!");
};

// Conditional toast chains
const handleUserAction = async (action) => {
  const loadingId = toast.loading(`Processing ${action}...`);
  
  try {
    const result = await performAction(action);
    
    if (result.requiresConfirmation) {
      toast.custom((id) => (
        <ConfirmationToast
          message={result.confirmationMessage}
          onConfirm={() => {
            toast.dismiss(id);
            finalizeAction(result);
          }}
          onCancel={() => toast.dismiss(id)}
        />
      ), { id: loadingId });
    } else {
      toast.success("Action completed!", { id: loadingId });
    }
  } catch (error) {
    toast.error(`Failed to ${action}`, { id: loadingId });
  }
};
```

## Types

```typescript { .api }
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;
}
```