or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-toast-features.mdcore-toast-functions.mdindex.mdreact-hooks.mdtoaster-component.md

react-hooks.mddocs/

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.