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

advanced-toast-features.mddocs/

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

```