0
# React Hooks
1
2
React hooks for integrating with toast state and managing toast notifications programmatically within React components.
3
4
## Capabilities
5
6
### useSonner Hook
7
8
React hook for accessing the current toast state, useful for building custom toast-aware components or debugging.
9
10
```typescript { .api }
11
/**
12
* React hook that provides access to current toast state
13
* @returns Object containing array of active toasts
14
*/
15
function useSonner(): {
16
toasts: ToastT[];
17
};
18
19
interface ToastT {
20
id: number | string;
21
title?: (() => React.ReactNode) | React.ReactNode;
22
type?: "normal" | "action" | "success" | "info" | "warning" | "error" | "loading" | "default";
23
icon?: React.ReactNode;
24
jsx?: React.ReactNode;
25
richColors?: boolean;
26
invert?: boolean;
27
closeButton?: boolean;
28
dismissible?: boolean;
29
description?: (() => React.ReactNode) | React.ReactNode;
30
duration?: number;
31
delete?: boolean;
32
action?: Action | React.ReactNode;
33
cancel?: Action | React.ReactNode;
34
onDismiss?: (toast: ToastT) => void;
35
onAutoClose?: (toast: ToastT) => void;
36
promise?: Promise<any> | (() => Promise<any>);
37
cancelButtonStyle?: React.CSSProperties;
38
actionButtonStyle?: React.CSSProperties;
39
style?: React.CSSProperties;
40
unstyled?: boolean;
41
className?: string;
42
classNames?: ToastClassnames;
43
descriptionClassName?: string;
44
position?: Position;
45
}
46
```
47
48
## Usage Examples
49
50
### Basic Toast State Access
51
52
```typescript
53
import React from "react";
54
import { useSonner, toast } from "sonner";
55
56
function ToastStatus() {
57
const { toasts } = useSonner();
58
59
return (
60
<div>
61
<p>Active toasts: {toasts.length}</p>
62
<button onClick={() => toast("New toast!")}>
63
Add Toast
64
</button>
65
<button onClick={() => toast.dismiss()}>
66
Clear All
67
</button>
68
</div>
69
);
70
}
71
```
72
73
### Toast Type Counter
74
75
```typescript
76
import React from "react";
77
import { useSonner, toast } from "sonner";
78
79
function ToastAnalytics() {
80
const { toasts } = useSonner();
81
82
const toastCounts = React.useMemo(() => {
83
return toasts.reduce((counts, toast) => {
84
const type = toast.type || "default";
85
counts[type] = (counts[type] || 0) + 1;
86
return counts;
87
}, {});
88
}, [toasts]);
89
90
return (
91
<div className="toast-analytics">
92
<h3>Current Toast Status</h3>
93
{Object.entries(toastCounts).map(([type, count]) => (
94
<div key={type}>
95
{type}: {count}
96
</div>
97
))}
98
99
<div className="controls">
100
<button onClick={() => toast.success("Success!")}>
101
Add Success
102
</button>
103
<button onClick={() => toast.error("Error!")}>
104
Add Error
105
</button>
106
<button onClick={() => toast.loading("Loading...")}>
107
Add Loading
108
</button>
109
</div>
110
</div>
111
);
112
}
113
```
114
115
### Loading State Indicator
116
117
```typescript
118
import React from "react";
119
import { useSonner } from "sonner";
120
121
function LoadingIndicator() {
122
const { toasts } = useSonner();
123
124
const hasLoadingToasts = toasts.some(toast => toast.type === "loading");
125
const loadingCount = toasts.filter(toast => toast.type === "loading").length;
126
127
if (!hasLoadingToasts) {
128
return null;
129
}
130
131
return (
132
<div className="loading-indicator">
133
<div className="spinner" />
134
<span>
135
{loadingCount === 1
136
? "Processing..."
137
: `${loadingCount} operations in progress...`
138
}
139
</span>
140
</div>
141
);
142
}
143
```
144
145
### Custom Toast Manager
146
147
```typescript
148
import React from "react";
149
import { useSonner, toast } from "sonner";
150
151
function ToastManager() {
152
const { toasts } = useSonner();
153
154
const dismissToast = (id: string | number) => {
155
toast.dismiss(id);
156
};
157
158
const dismissByType = (type: string) => {
159
toasts
160
.filter(t => t.type === type)
161
.forEach(t => toast.dismiss(t.id));
162
};
163
164
const dismissOlderThan = (minutes: number) => {
165
const cutoff = Date.now() - (minutes * 60 * 1000);
166
toasts
167
.filter(t => {
168
// Approximate age based on ID if it's a timestamp
169
const toastTime = typeof t.id === 'number' ? t.id : Date.now();
170
return toastTime < cutoff;
171
})
172
.forEach(t => toast.dismiss(t.id));
173
};
174
175
return (
176
<div className="toast-manager">
177
<h3>Toast Manager ({toasts.length} active)</h3>
178
179
<div className="toast-list">
180
{toasts.map(toast => (
181
<div key={toast.id} className="toast-item">
182
<span className={`toast-type ${toast.type}`}>
183
{toast.type || "default"}
184
</span>
185
<span className="toast-title">
186
{typeof toast.title === "function" ? toast.title() : toast.title}
187
</span>
188
<button onClick={() => dismissToast(toast.id)}>
189
×
190
</button>
191
</div>
192
))}
193
</div>
194
195
<div className="controls">
196
<button onClick={() => dismissByType("error")}>
197
Clear Errors
198
</button>
199
<button onClick={() => dismissByType("success")}>
200
Clear Success
201
</button>
202
<button onClick={() => dismissOlderThan(5)}>
203
Clear Old (5+ min)
204
</button>
205
<button onClick={() => toast.dismiss()}>
206
Clear All
207
</button>
208
</div>
209
</div>
210
);
211
}
212
```
213
214
### Toast Persistence Monitor
215
216
```typescript
217
import React from "react";
218
import { useSonner } from "sonner";
219
220
function ToastPersistenceMonitor() {
221
const { toasts } = useSonner();
222
const [persistentToasts, setPersistentToasts] = React.useState([]);
223
224
React.useEffect(() => {
225
// Track toasts that have been around for a long time
226
const persistent = toasts.filter(toast => {
227
// Check if duration is Infinity or very long
228
return toast.duration === Infinity ||
229
(toast.duration && toast.duration > 30000) ||
230
toast.type === "loading";
231
});
232
233
setPersistentToasts(persistent);
234
}, [toasts]);
235
236
if (persistentToasts.length === 0) {
237
return null;
238
}
239
240
return (
241
<div className="persistence-monitor">
242
<h4>⚠️ Persistent Toasts ({persistentToasts.length})</h4>
243
<p>These toasts won't auto-dismiss:</p>
244
<ul>
245
{persistentToasts.map(toast => (
246
<li key={toast.id}>
247
{toast.type}: {typeof toast.title === "function" ? toast.title() : toast.title}
248
</li>
249
))}
250
</ul>
251
</div>
252
);
253
}
254
```
255
256
### Performance Monitor
257
258
```typescript
259
import React from "react";
260
import { useSonner } from "sonner";
261
262
function ToastPerformanceMonitor() {
263
const { toasts } = useSonner();
264
const [maxToasts, setMaxToasts] = React.useState(0);
265
const [toastHistory, setToastHistory] = React.useState([]);
266
267
React.useEffect(() => {
268
setMaxToasts(Math.max(maxToasts, toasts.length));
269
270
// Keep a history of toast count changes
271
setToastHistory(prev => [
272
...prev.slice(-20), // Keep last 20 entries
273
{ timestamp: Date.now(), count: toasts.length }
274
]);
275
}, [toasts.length]);
276
277
const averageToasts = React.useMemo(() => {
278
if (toastHistory.length === 0) return 0;
279
const sum = toastHistory.reduce((acc, entry) => acc + entry.count, 0);
280
return (sum / toastHistory.length).toFixed(1);
281
}, [toastHistory]);
282
283
return (
284
<div className="performance-monitor">
285
<h4>Toast Performance</h4>
286
<div className="metrics">
287
<div>Current: {toasts.length}</div>
288
<div>Peak: {maxToasts}</div>
289
<div>Average: {averageToasts}</div>
290
</div>
291
292
{toasts.length > 5 && (
293
<div className="warning">
294
⚠️ High toast count may impact performance
295
</div>
296
)}
297
</div>
298
);
299
}
300
```
301
302
### Conditional Rendering Based on Toasts
303
304
```typescript
305
import React from "react";
306
import { useSonner } from "sonner";
307
308
function AppWithToastAwareness() {
309
const { toasts } = useSonner();
310
311
const hasErrors = toasts.some(t => t.type === "error");
312
const hasLoading = toasts.some(t => t.type === "loading");
313
const hasSuccessToasts = toasts.some(t => t.type === "success");
314
315
return (
316
<div className={`app ${hasErrors ? "has-errors" : ""}`}>
317
<header>
318
<h1>My App</h1>
319
{hasLoading && <div className="loading-indicator">Processing...</div>}
320
</header>
321
322
<main>
323
{hasErrors && (
324
<div className="error-state">
325
<p>Some operations failed. Check notifications for details.</p>
326
</div>
327
)}
328
329
{hasSuccessToasts && (
330
<div className="success-state">
331
<p>✅ Recent successful operations</p>
332
</div>
333
)}
334
335
<YourAppContent />
336
</main>
337
338
{toasts.length > 0 && (
339
<div className="toast-summary">
340
{toasts.length} active notification{toasts.length !== 1 ? "s" : ""}
341
</div>
342
)}
343
</div>
344
);
345
}
346
```
347
348
## Advanced Patterns
349
350
### Custom Toast Provider
351
352
```typescript
353
import React from "react";
354
import { useSonner, toast } from "sonner";
355
356
const ToastContext = React.createContext({
357
toasts: [],
358
addToast: (message, options) => {},
359
clearToasts: () => {},
360
hasToasts: false,
361
});
362
363
export function ToastProvider({ children }) {
364
const { toasts } = useSonner();
365
366
const contextValue = React.useMemo(() => ({
367
toasts,
368
addToast: (message, options) => toast(message, options),
369
clearToasts: () => toast.dismiss(),
370
hasToasts: toasts.length > 0,
371
}), [toasts]);
372
373
return (
374
<ToastContext.Provider value={contextValue}>
375
{children}
376
</ToastContext.Provider>
377
);
378
}
379
380
export function useToastContext() {
381
return React.useContext(ToastContext);
382
}
383
```
384
385
### Toast State Synchronization
386
387
```typescript
388
import React from "react";
389
import { useSonner } from "sonner";
390
391
function useToastSync() {
392
const { toasts } = useSonner();
393
394
// Sync toast state to localStorage
395
React.useEffect(() => {
396
const toastData = toasts.map(t => ({
397
id: t.id,
398
type: t.type,
399
title: typeof t.title === "string" ? t.title : "[React Element]",
400
timestamp: Date.now()
401
}));
402
403
localStorage.setItem("recent-toasts", JSON.stringify(toastData));
404
}, [toasts]);
405
406
// Analytics tracking
407
React.useEffect(() => {
408
if (typeof window !== "undefined" && window.gtag) {
409
toasts.forEach(toast => {
410
if (toast.type === "error") {
411
window.gtag("event", "toast_error", {
412
error_type: toast.type,
413
error_message: toast.title
414
});
415
}
416
});
417
}
418
}, [toasts]);
419
}
420
```
421
422
## Common Use Cases
423
424
1. **Debug Panel**: Display current toast state during development
425
2. **Analytics**: Track toast usage patterns and error rates
426
3. **Performance Monitoring**: Watch for excessive toast creation
427
4. **Conditional UI**: Show/hide elements based on toast presence
428
5. **State Synchronization**: Sync toast state with external systems
429
6. **Custom Management**: Build advanced toast management interfaces
430
431
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.