0
# Advanced Toast Features
1
2
Advanced toast functionality including promise integration, custom JSX content, action buttons, and state management utilities for complex notification scenarios.
3
4
## Capabilities
5
6
### Promise Toast Integration
7
8
Automatically manages toast states for asynchronous operations with loading, success, and error states.
9
10
```typescript { .api }
11
/**
12
* Creates a toast that tracks a promise's lifecycle
13
* @param promise - Promise or function that returns a promise
14
* @param options - Configuration for different promise states
15
* @returns Object with unwrap method and toast ID
16
*/
17
function toast.promise<T>(
18
promise: Promise<T> | (() => Promise<T>),
19
options: PromiseData<T>
20
): { unwrap(): Promise<T> } & (string | number);
21
22
interface PromiseData<ToastData = any> {
23
/** Message or component to show during loading state */
24
loading?: string | React.ReactNode;
25
/** Message, component, or function to show on success */
26
success?: string | React.ReactNode | ((data: ToastData) => React.ReactNode | string | Promise<React.ReactNode | string>);
27
/** Message, component, or function to show on error */
28
error?: string | React.ReactNode | ((error: any) => React.ReactNode | string | Promise<React.ReactNode | string>);
29
/** Description that can be dynamic based on result */
30
description?: string | React.ReactNode | ((data: any) => React.ReactNode | string | Promise<React.ReactNode | string>);
31
/** Callback executed regardless of promise outcome */
32
finally?: () => void | Promise<void>;
33
}
34
```
35
36
**Usage Examples:**
37
38
```typescript
39
import { toast } from "sonner";
40
41
// Basic promise toast
42
const uploadPromise = uploadFile(file);
43
44
toast.promise(uploadPromise, {
45
loading: "Uploading file...",
46
success: "File uploaded successfully!",
47
error: "Failed to upload file"
48
});
49
50
// Dynamic success message
51
toast.promise(saveUserProfile(userData), {
52
loading: "Saving profile...",
53
success: (data) => `Profile saved! User ID: ${data.id}`,
54
error: (error) => `Save failed: ${error.message}`
55
});
56
57
// With description and finally callback
58
toast.promise(processData(), {
59
loading: "Processing your data...",
60
success: "Data processed successfully",
61
error: "Processing failed",
62
description: (result) => `Processed ${result.count} items`,
63
finally: () => {
64
console.log("Processing completed");
65
refreshUI();
66
}
67
});
68
69
// Function-based promise
70
toast.promise(
71
() => fetch("/api/data").then(res => res.json()),
72
{
73
loading: "Fetching data...",
74
success: "Data loaded!",
75
error: "Failed to load data"
76
}
77
);
78
79
// Using unwrap to handle the original promise
80
const promiseToast = toast.promise(asyncOperation(), {
81
loading: "Working...",
82
success: "Done!",
83
error: "Failed!"
84
});
85
86
// Access the original promise result
87
try {
88
const result = await promiseToast.unwrap();
89
console.log("Operation result:", result);
90
} catch (error) {
91
console.error("Operation failed:", error);
92
}
93
```
94
95
### Custom JSX Toasts
96
97
Create completely custom toast content using React components.
98
99
```typescript { .api }
100
/**
101
* Creates a toast with custom JSX content
102
* @param jsx - Function that receives toast ID and returns React element
103
* @param options - Additional configuration options
104
* @returns Unique identifier for the created toast
105
*/
106
function toast.custom(
107
jsx: (id: string | number) => React.ReactElement,
108
options?: ExternalToast
109
): string | number;
110
```
111
112
**Usage Examples:**
113
114
```typescript
115
import { toast } from "sonner";
116
117
// Simple custom toast
118
toast.custom((t) => (
119
<div style={{ padding: "12px" }}>
120
<h3>Custom Toast</h3>
121
<p>This is completely custom content!</p>
122
<button onClick={() => toast.dismiss(t)}>
123
Close
124
</button>
125
</div>
126
));
127
128
// Complex custom toast with actions
129
toast.custom((t) => (
130
<div className="custom-toast">
131
<div className="toast-header">
132
<strong>New Message</strong>
133
<small>2 minutes ago</small>
134
</div>
135
<div className="toast-body">
136
<p>You have received a new message from John Doe.</p>
137
</div>
138
<div className="toast-actions">
139
<button
140
onClick={() => {
141
viewMessage();
142
toast.dismiss(t);
143
}}
144
className="btn-primary"
145
>
146
View
147
</button>
148
<button
149
onClick={() => toast.dismiss(t)}
150
className="btn-secondary"
151
>
152
Dismiss
153
</button>
154
</div>
155
</div>
156
));
157
158
// Custom toast with form
159
toast.custom((t) => {
160
const [email, setEmail] = React.useState("");
161
162
const handleSubmit = (e) => {
163
e.preventDefault();
164
subscribeToNewsletter(email);
165
toast.dismiss(t);
166
toast.success("Subscribed successfully!");
167
};
168
169
return (
170
<form onSubmit={handleSubmit} className="newsletter-toast">
171
<h4>Subscribe to Newsletter</h4>
172
<input
173
type="email"
174
placeholder="Enter your email"
175
value={email}
176
onChange={(e) => setEmail(e.target.value)}
177
required
178
/>
179
<div className="form-actions">
180
<button type="submit">Subscribe</button>
181
<button type="button" onClick={() => toast.dismiss(t)}>
182
Cancel
183
</button>
184
</div>
185
</form>
186
);
187
});
188
189
// Custom toast with options
190
toast.custom(
191
(t) => <CustomNotificationComponent id={t} />,
192
{
193
duration: Infinity, // Keep open until manually dismissed
194
position: "top-center"
195
}
196
);
197
```
198
199
### Toast State Management
200
201
Functions for managing toast state and retrieving toast information.
202
203
```typescript { .api }
204
/**
205
* Gets all toasts in history (including dismissed ones)
206
* @returns Array of all toast objects
207
*/
208
function toast.getHistory(): ToastT[];
209
210
/**
211
* Gets currently active (visible) toasts
212
* @returns Array of active toast objects
213
*/
214
function toast.getToasts(): ToastT[];
215
```
216
217
**Usage Examples:**
218
219
```typescript
220
import { toast } from "sonner";
221
222
// Check current toast count
223
const activeToasts = toast.getToasts();
224
console.log(`Currently showing ${activeToasts.length} toasts`);
225
226
// Conditional toast creation
227
if (toast.getToasts().length < 3) {
228
toast("New notification");
229
} else {
230
console.log("Too many toasts, skipping...");
231
}
232
233
// Get toast history for analytics
234
const allToasts = toast.getHistory();
235
const errorToasts = allToasts.filter(t => t.type === "error");
236
console.log(`User saw ${errorToasts.length} error messages`);
237
238
// Find specific toast
239
const loadingToasts = toast.getToasts().filter(t => t.type === "loading");
240
if (loadingToasts.length > 0) {
241
console.log("Operations still in progress");
242
}
243
```
244
245
### Toast Updates and Management
246
247
Update existing toasts or manage multiple related toasts.
248
249
```typescript { .api }
250
// Update existing toast by providing same ID
251
const toastId = toast("Initial message", { duration: 10000 });
252
253
// Later, update the same toast
254
toast("Updated message", { id: toastId });
255
256
// Update with different type
257
toast.success("Completed!", { id: toastId });
258
```
259
260
**Usage Examples:**
261
262
```typescript
263
import { toast } from "sonner";
264
265
// Progress tracking
266
const progressId = toast.loading("Starting process...");
267
268
setTimeout(() => {
269
toast.loading("Step 1 of 3: Downloading...", { id: progressId });
270
}, 1000);
271
272
setTimeout(() => {
273
toast.loading("Step 2 of 3: Processing...", { id: progressId });
274
}, 3000);
275
276
setTimeout(() => {
277
toast.success("Process completed!", { id: progressId });
278
}, 5000);
279
280
// Multi-step form handling
281
let formToastId: string | number;
282
283
const handleFormStart = () => {
284
formToastId = toast.loading("Validating form...");
285
};
286
287
const handleFormValidated = () => {
288
toast.loading("Submitting data...", { id: formToastId });
289
};
290
291
const handleFormComplete = (result) => {
292
if (result.success) {
293
toast.success("Form submitted successfully!", { id: formToastId });
294
} else {
295
toast.error(`Submission failed: ${result.error}`, { id: formToastId });
296
}
297
};
298
299
// Batch operations
300
const batchId = toast.loading("Processing 0/100 items...");
301
302
for (let i = 0; i < 100; i++) {
303
await processItem(i);
304
305
// Update progress every 10 items
306
if (i % 10 === 0) {
307
toast.loading(`Processing ${i}/100 items...`, { id: batchId });
308
}
309
}
310
311
toast.success("All items processed!", { id: batchId });
312
```
313
314
## Advanced Patterns
315
316
### Promise Error Handling
317
318
```typescript
319
// Handle different HTTP error codes
320
toast.promise(apiCall(), {
321
loading: "Saving...",
322
success: "Saved successfully!",
323
error: (error) => {
324
if (error.status === 401) {
325
return "Please log in to continue";
326
} else if (error.status === 403) {
327
return "You don't have permission to do this";
328
} else if (error.status >= 500) {
329
return "Server error. Please try again later";
330
}
331
return "Something went wrong";
332
}
333
});
334
335
// Promise with retry functionality
336
const attemptOperation = async (retryCount = 0) => {
337
try {
338
const result = await riskyOperation();
339
return result;
340
} catch (error) {
341
if (retryCount < 3) {
342
throw error; // Let promise toast handle it
343
}
344
throw new Error("Max retries exceeded");
345
}
346
};
347
348
toast.promise(attemptOperation(), {
349
loading: "Attempting operation...",
350
success: "Operation successful!",
351
error: (error) => {
352
return (
353
<div>
354
<p>Operation failed: {error.message}</p>
355
<button onClick={() => toast.promise(attemptOperation(), { /* ... */ })}>
356
Retry
357
</button>
358
</div>
359
);
360
}
361
});
362
```
363
364
### Complex Custom Components
365
366
```typescript
367
// Toast with image and rich content
368
const MediaToast = ({ id, media, onAction }) => (
369
<div className="media-toast">
370
<img src={media.thumbnail} alt="" className="media-thumbnail" />
371
<div className="media-content">
372
<h4>{media.title}</h4>
373
<p>{media.description}</p>
374
<div className="media-actions">
375
<button onClick={() => onAction('play')}>Play</button>
376
<button onClick={() => onAction('download')}>Download</button>
377
<button onClick={() => toast.dismiss(id)}>×</button>
378
</div>
379
</div>
380
</div>
381
);
382
383
// Usage
384
toast.custom((id) => (
385
<MediaToast
386
id={id}
387
media={mediaItem}
388
onAction={(action) => handleMediaAction(action, mediaItem)}
389
/>
390
));
391
392
// Toast with live updates
393
const LiveUpdateToast = ({ id, initialData }) => {
394
const [data, setData] = React.useState(initialData);
395
396
React.useEffect(() => {
397
const subscription = subscribeToUpdates((newData) => {
398
setData(newData);
399
});
400
401
return () => subscription.unsubscribe();
402
}, []);
403
404
return (
405
<div className="live-toast">
406
<h4>Live Data: {data.value}</h4>
407
<small>Last updated: {data.timestamp}</small>
408
<button onClick={() => toast.dismiss(id)}>Close</button>
409
</div>
410
);
411
};
412
```
413
414
### Toast Orchestration
415
416
```typescript
417
// Sequential toasts
418
const showSequentialToasts = async () => {
419
const step1 = toast.loading("Step 1: Preparing...");
420
await delay(2000);
421
422
toast.success("Step 1 complete", { id: step1 });
423
await delay(1000);
424
425
const step2 = toast.loading("Step 2: Processing...");
426
await delay(3000);
427
428
toast.success("Step 2 complete", { id: step2 });
429
await delay(1000);
430
431
toast.success("All steps completed!");
432
};
433
434
// Conditional toast chains
435
const handleUserAction = async (action) => {
436
const loadingId = toast.loading(`Processing ${action}...`);
437
438
try {
439
const result = await performAction(action);
440
441
if (result.requiresConfirmation) {
442
toast.custom((id) => (
443
<ConfirmationToast
444
message={result.confirmationMessage}
445
onConfirm={() => {
446
toast.dismiss(id);
447
finalizeAction(result);
448
}}
449
onCancel={() => toast.dismiss(id)}
450
/>
451
), { id: loadingId });
452
} else {
453
toast.success("Action completed!", { id: loadingId });
454
}
455
} catch (error) {
456
toast.error(`Failed to ${action}`, { id: loadingId });
457
}
458
};
459
```
460
461
## Types
462
463
```typescript { .api }
464
interface ToastT {
465
id: number | string;
466
title?: (() => React.ReactNode) | React.ReactNode;
467
type?: "normal" | "action" | "success" | "info" | "warning" | "error" | "loading" | "default";
468
icon?: React.ReactNode;
469
jsx?: React.ReactNode;
470
richColors?: boolean;
471
invert?: boolean;
472
closeButton?: boolean;
473
dismissible?: boolean;
474
description?: (() => React.ReactNode) | React.ReactNode;
475
duration?: number;
476
delete?: boolean;
477
action?: Action | React.ReactNode;
478
cancel?: Action | React.ReactNode;
479
onDismiss?: (toast: ToastT) => void;
480
onAutoClose?: (toast: ToastT) => void;
481
promise?: Promise<any> | (() => Promise<any>);
482
cancelButtonStyle?: React.CSSProperties;
483
actionButtonStyle?: React.CSSProperties;
484
style?: React.CSSProperties;
485
unstyled?: boolean;
486
className?: string;
487
classNames?: ToastClassnames;
488
descriptionClassName?: string;
489
position?: Position;
490
}
491
```