@dismissible/react-client 1.0.0 → 2.0.0-canary.5.88c27fb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # [2.0.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v1.0.0...v2.0.0) (2026-01-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **test:** fixed test race condition ([#6](https://github.com/DismissibleIo/dismissible-react-client/issues/6)) ([da56324](https://github.com/DismissibleIo/dismissible-react-client/commit/da5632458c61a734d7bd55c89ef28d78afeabbf4))
7
+
8
+
9
+ ### Features
10
+
11
+ * **api:** aligned with api response ([#5](https://github.com/DismissibleIo/dismissible-react-client/issues/5)) ([8fe9f87](https://github.com/DismissibleIo/dismissible-react-client/commit/8fe9f879691f88cb761a8b4293c824a6d4477db2))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * **api:** You need to update any of your hook consumers to check dismissedAt instead of
17
+ dismissedOn. This aligns with the API response.
18
+
19
+ # 1.0.0 (2025-12-21)
20
+
21
+
22
+ ### Features
23
+
24
+ * **react-client:** dismissible React Client ([#1](https://github.com/DismissibleIo/dismissible-react-client/issues/1)) ([a4e6dc7](https://github.com/DismissibleIo/dismissible-react-client/commit/a4e6dc7786a4fd1abb81f4a796e22d37fdc12e0b))
25
+
26
+
27
+ ### BREAKING CHANGES
28
+
29
+ * **react-client:** This is the first public release which requires both the DismissibleProvider and
30
+ Dismissible components to be used to force a userId.
package/README.md CHANGED
@@ -1,26 +1,38 @@
1
- # @dismissible/react-client
1
+ <p align="center">
2
+ <a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a>
3
+ </p>
4
+
5
+ <p align="center">Never Show The Same Thing Twice!</p>
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/v/@dismissible/react-client.svg" alt="NPM Version" /></a>
8
+ <a href="https://github.com/dismissibleio/dismissible-react-client/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@dismissible/react-client.svg" alt="Package License" /></a>
9
+ <a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/dm/@dismissible/react-client.svg" alt="NPM Downloads" /></a>
10
+ <a href="https://github.com/dismissibleio/dismissible-react-client" target="_blank"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/dismissibleio/dismissible-react-client/publish.yml" /></a>
11
+ <a href="https://paypal.me/joshstuartx" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" /></a>
12
+ </p>
2
13
 
3
- A React component library for creating dismissible UI elements with persistent state management.
14
+ Dismissible manages the state of your UI elements across sessions, so your users see what matters, once! No more onboarding messages reappearing on every tab, no more notifications haunting users across devices. Dismissible syncs dismissal state everywhere, so every message is intentional, never repetitive.
15
+
16
+ # @dismissible/react-client
4
17
 
5
- **Free and open source** - use with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api) that you can self-host with Docker or integrate into your NestJS application.
18
+ This is the React component library for creating dismissible UI elements with persistent state management.
6
19
 
7
- 🌐 **[dismissible.io](https://dismissible.io)** | 📖 **[Documentation](https://dismissible.io/docs)** | 🐙 **[API Server](https://github.com/DismissibleIo/dismissible-api)**
20
+ This component is used with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api), which you can self-host with Docker or integrate into your NestJS application.
8
21
 
9
- [![npm version](https://badge.fury.io/js/@dismissible%2Freact-client.svg)](https://badge.fury.io/js/@dismissible%2Freact-client)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
+ **[dismissible.io](https://dismissible.io)** | **[Documentation](https://dismissible.io/docs)** | **[API Server](https://github.com/DismissibleIo/dismissible-api)**
11
23
 
12
24
  ## Features
13
25
 
14
- - 🎯 **Easy to use** - Simple component API for dismissible content
15
- - 💾 **Persistent state** - Dismissal state is saved and restored across sessions
16
- - 🔄 **Restore support** - Restore previously dismissed items programmatically
17
- - 🔐 **JWT Authentication** - Built-in support for secure JWT-based authentication
18
- - 🎨 **Customizable** - Custom loading, error, and dismiss button components
19
- - **Accessible** - Built with accessibility best practices
20
- - 🪝 **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
21
- - 📦 **Lightweight** - Minimal bundle size with tree-shaking support
22
- - 🔧 **TypeScript** - Full TypeScript support with complete type definitions
23
- - 🐳 **Self-hosted** - Works with your own Dismissible API server
26
+ - **Easy to use** - Simple component API for dismissible content
27
+ - **Persistent state** - Dismissal state is saved and restored across sessions when using the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api)
28
+ - **Restore support** - Restore previously dismissed items programmatically
29
+ - **JWT Authentication** - Built-in support for secure JWT-based authentication
30
+ - **Custom HTTP Client** - Bring your own HTTP client (axios, ky, etc.) with custom headers, interceptors, and tracking
31
+ - **Customizable** - Custom loading, error, and dismiss button components
32
+ - **Accessible** - Built with accessibility best practices
33
+ - **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
34
+ - **Lightweight** - Minimal bundle size with tree-shaking support
35
+ - **TypeScript** - Full TypeScript support with complete type definitions
24
36
 
25
37
  ## Installation
26
38
 
@@ -40,7 +52,7 @@ npm install react react-dom
40
52
 
41
53
  ### 1. Set up the Dismissible API Server
42
54
 
43
- First, you need a Dismissible API server running. The easiest way is with Docker:
55
+ First, you need a [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api). The easiest way is with Docker:
44
56
 
45
57
  ```yaml
46
58
  # docker-compose.yml
@@ -52,38 +64,29 @@ services:
52
64
  - '3001:3001'
53
65
  environment:
54
66
  DISMISSIBLE_PORT: 3001
55
- DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING: postgresql://postgres:postgres@db:5432/dismissible
56
- depends_on:
57
- - db
58
-
59
- db:
60
- image: postgres:15
61
- environment:
62
- POSTGRES_USER: postgres
63
- POSTGRES_PASSWORD: postgres
64
- POSTGRES_DB: dismissible
65
- volumes:
66
- - postgres_data:/var/lib/postgresql/data
67
-
68
- volumes:
69
- postgres_data:
70
67
  ```
71
68
 
72
69
  ```bash
73
70
  docker-compose up -d
74
71
  ```
75
72
 
76
- See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration.
73
+ OR
74
+
75
+ ```bash
76
+ docker run -p 3001:3001 -e DISMISSIBLE_PORT=3001 dismissibleio/dismissible-api:latest
77
+ ```
78
+
79
+ See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration, public Docker image and more.
77
80
 
78
81
  ### 2. Configure the Provider
79
82
 
80
- Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track dismissals per user:
83
+ Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track all your dismissals per user:
81
84
 
82
85
  ```tsx
83
86
  import { DismissibleProvider } from '@dismissible/react-client';
84
87
 
85
88
  function App() {
86
- const userId = getCurrentUserId(); // Get from your auth system
89
+ const userId = getCurrentUserId();
87
90
 
88
91
  return (
89
92
  <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
@@ -94,6 +97,7 @@ function App() {
94
97
  ```
95
98
 
96
99
  ### 3. Use Dismissible Components
100
+ Now wrap any component you want to be dismissible with the `<Dismissible>` component, and the `itemId`, along with the `userId`, will become the unique key that is tracked across sessions and devices.
97
101
 
98
102
  ```tsx
99
103
  import { Dismissible } from '@dismissible/react-client';
@@ -121,8 +125,9 @@ Context provider that configures authentication and API settings. **Required** -
121
125
  | Prop | Type | Required | Description |
122
126
  |------|------|----------|-------------|
123
127
  | `userId` | `string` | ✅ | User ID for tracking dismissals per user |
128
+ | `baseUrl` | `string` | ✅ | API base URL for your self-hosted server |
124
129
  | `jwt` | `string \| (() => string) \| (() => Promise<string>)` | ❌ | JWT token for secure authentication |
125
- | `baseUrl` | `string` | ❌ | API base URL (defaults to your self-hosted server) |
130
+ | `client` | `DismissibleClient` | ❌ | Custom HTTP client implementation (uses default if not provided) |
126
131
  | `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
127
132
 
128
133
  #### Example
@@ -183,6 +188,8 @@ function AppWithAsyncAuth() {
183
188
  }
184
189
  ```
185
190
 
191
+ See [Custom HTTP Client](#custom-http-client) for examples of using a custom client.
192
+
186
193
  ### `<Dismissible>` Component
187
194
 
188
195
  The main component for creating dismissible content.
@@ -242,11 +249,11 @@ For custom implementations and advanced use cases.
242
249
 
243
250
  | Property | Type | Description |
244
251
  |----------|------|-------------|
245
- | `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
252
+ | `dismissedAt` | `string \| undefined` | ISO date string when item was dismissed, or undefined |
246
253
  | `dismiss` | `() => Promise<void>` | Function to dismiss the item |
247
254
  | `restore` | `() => Promise<void>` | Function to restore a dismissed item |
248
255
  | `isLoading` | `boolean` | Loading state indicator |
249
- | `error` | `Error \| null` | Error state, if any |
256
+ | `error` | `Error \| undefined` | Error state, if any |
250
257
  | `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
251
258
 
252
259
  #### Example
@@ -255,7 +262,7 @@ For custom implementations and advanced use cases.
255
262
  import { useDismissibleItem } from '@dismissible/react-client';
256
263
 
257
264
  function CustomDismissible({ itemId, children }) {
258
- const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
265
+ const { dismissedAt, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
259
266
 
260
267
  if (isLoading) {
261
268
  return <div>Loading...</div>;
@@ -265,7 +272,7 @@ function CustomDismissible({ itemId, children }) {
265
272
  return <div>Error: {error.message}</div>;
266
273
  }
267
274
 
268
- if (dismissedOn) {
275
+ if (dismissedAt) {
269
276
  return (
270
277
  <div>
271
278
  <p>This item was dismissed.</p>
@@ -338,7 +345,6 @@ function App() {
338
345
  function Dashboard() {
339
346
  return (
340
347
  <div>
341
- {/* Dismissible state is tracked per user */}
342
348
  <Dismissible itemId="user-welcome-banner">
343
349
  <div className="alert alert-info">
344
350
  <h4>Welcome back!</h4>
@@ -427,19 +433,19 @@ function Dashboard() {
427
433
  <div>
428
434
  <Dismissible itemId="feature-announcement">
429
435
  <div className="alert alert-success">
430
- 🎉 New feature: Dark mode is now available!
436
+ New feature: Dark mode is now available!
431
437
  </div>
432
438
  </Dismissible>
433
439
 
434
440
  <Dismissible itemId="maintenance-notice">
435
441
  <div className="alert alert-warning">
436
- ⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
442
+ Scheduled maintenance: Sunday 2AM-4AM EST
437
443
  </div>
438
444
  </Dismissible>
439
445
 
440
446
  <Dismissible itemId="survey-request">
441
447
  <div className="alert alert-info">
442
- 📝 Help us improve! Take our 2-minute survey.
448
+ Help us improve! Take our 2-minute survey.
443
449
  </div>
444
450
  </Dismissible>
445
451
  </div>
@@ -468,74 +474,6 @@ function RobustBanner() {
468
474
  }
469
475
  ```
470
476
 
471
- ### Integration with Auth Providers
472
-
473
- ```tsx
474
- import { DismissibleProvider } from '@dismissible/react-client';
475
-
476
- // With Firebase Auth
477
- function AppWithFirebase() {
478
- const user = firebase.auth().currentUser;
479
-
480
- return (
481
- <DismissibleProvider
482
- userId={user.uid}
483
- jwt={async () => {
484
- if (user) {
485
- return await user.getIdToken();
486
- }
487
- throw new Error('User not authenticated');
488
- }}
489
- baseUrl="https://api.yourapp.com"
490
- >
491
- <YourApp />
492
- </DismissibleProvider>
493
- );
494
- }
495
-
496
- // With Auth0
497
- function AppWithAuth0() {
498
- const { user, getAccessTokenSilently } = useAuth0();
499
-
500
- return (
501
- <DismissibleProvider
502
- userId={user.sub}
503
- jwt={async () => await getAccessTokenSilently()}
504
- baseUrl="https://api.yourapp.com"
505
- >
506
- <YourApp />
507
- </DismissibleProvider>
508
- );
509
- }
510
-
511
- // With token refresh logic
512
- function AppWithTokenRefresh() {
513
- const { user } = useAuth();
514
-
515
- return (
516
- <DismissibleProvider
517
- userId={user.id}
518
- jwt={async () => {
519
- try {
520
- const response = await fetch('/api/auth/refresh', {
521
- method: 'POST',
522
- credentials: 'include'
523
- });
524
- const { accessToken } = await response.json();
525
- return accessToken;
526
- } catch (error) {
527
- console.error('Failed to refresh token:', error);
528
- throw error;
529
- }
530
- }}
531
- baseUrl="https://api.yourapp.com"
532
- >
533
- <YourApp />
534
- </DismissibleProvider>
535
- );
536
- }
537
- ```
538
-
539
477
  ### Using the Hook for Complex Logic
540
478
 
541
479
  ```tsx
@@ -543,12 +481,12 @@ import { useDismissibleItem } from '@dismissible/react-client';
543
481
  import { useState, useEffect } from 'react';
544
482
 
545
483
  function SmartNotification({ itemId, message, type = 'info' }) {
546
- const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
484
+ const { dismissedAt, dismiss, isLoading } = useDismissibleItem(itemId);
547
485
  const [autoHide, setAutoHide] = useState(false);
548
486
 
549
487
  // Auto-hide after 10 seconds for info messages
550
488
  useEffect(() => {
551
- if (type === 'info' && !dismissedOn) {
489
+ if (type === 'info' && !dismissedAt) {
552
490
  const timer = setTimeout(() => {
553
491
  setAutoHide(true);
554
492
  dismiss();
@@ -556,9 +494,9 @@ function SmartNotification({ itemId, message, type = 'info' }) {
556
494
 
557
495
  return () => clearTimeout(timer);
558
496
  }
559
- }, [type, dismissedOn, dismiss]);
497
+ }, [type, dismissedAt, dismiss]);
560
498
 
561
- if (dismissedOn || autoHide) {
499
+ if (dismissedAt || autoHide) {
562
500
  return null;
563
501
  }
564
502
 
@@ -585,12 +523,12 @@ Use the `restore` function to bring back previously dismissed content:
585
523
  import { useDismissibleItem } from '@dismissible/react-client';
586
524
 
587
525
  function RestorableBanner({ itemId }) {
588
- const { dismissedOn, dismiss, restore, isLoading } = useDismissibleItem(itemId);
526
+ const { dismissedAt, dismiss, restore, isLoading } = useDismissibleItem(itemId);
589
527
 
590
- if (dismissedOn) {
528
+ if (dismissedAt) {
591
529
  return (
592
530
  <div className="dismissed-placeholder">
593
- <p>Banner was dismissed on {new Date(dismissedOn).toLocaleDateString()}</p>
531
+ <p>Banner was dismissed on {new Date(dismissedAt).toLocaleDateString()}</p>
594
532
  <button onClick={restore} disabled={isLoading}>
595
533
  {isLoading ? 'Restoring...' : 'Show Banner Again'}
596
534
  </button>
@@ -610,51 +548,254 @@ function RestorableBanner({ itemId }) {
610
548
  }
611
549
  ```
612
550
 
613
- ### Admin Panel with Restore Capability
551
+ ## Advanced Usage
552
+
553
+ ### Custom HTTP Client
554
+
555
+ By default, Dismissible uses a built-in HTTP client powered by `openapi-fetch`. However, you can provide your own HTTP client implementation by passing a `client` prop to the `DismissibleProvider`. This is useful when you need:
556
+
557
+ - Custom headers (correlation IDs, tracing, etc.)
558
+ - Request/response interceptors
559
+ - Use a different HTTP library (axios, ky, etc.)
560
+ - Analytics and logging
561
+ - Custom error handling
562
+
563
+ #### The DismissibleClient Interface
564
+
565
+ Your custom client must implement the `DismissibleClient` interface:
566
+
567
+ ```typescript
568
+ import type { DismissibleClient, DismissibleItem } from '@dismissible/react-client';
569
+
570
+ interface DismissibleClient {
571
+ getOrCreate: (params: {
572
+ userId: string;
573
+ itemId: string;
574
+ baseUrl: string;
575
+ authHeaders: { Authorization?: string };
576
+ signal?: AbortSignal;
577
+ }) => Promise<DismissibleItem>;
578
+
579
+ dismiss: (params: {
580
+ userId: string;
581
+ itemId: string;
582
+ baseUrl: string;
583
+ authHeaders: { Authorization?: string };
584
+ }) => Promise<DismissibleItem>;
585
+
586
+ restore: (params: {
587
+ userId: string;
588
+ itemId: string;
589
+ baseUrl: string;
590
+ authHeaders: { Authorization?: string };
591
+ }) => Promise<DismissibleItem>;
592
+ }
593
+ ```
594
+
595
+ #### Example: Custom Client with Axios
614
596
 
615
597
  ```tsx
616
- import { useDismissibleItem } from '@dismissible/react-client';
598
+ import axios from 'axios';
599
+ import { v4 as uuid } from 'uuid';
600
+ import { DismissibleProvider } from '@dismissible/react-client';
601
+ import type { DismissibleClient } from '@dismissible/react-client';
602
+
603
+ const axiosClient: DismissibleClient = {
604
+ getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
605
+ const response = await axios.get(
606
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
607
+ {
608
+ headers: {
609
+ ...authHeaders,
610
+ 'X-Correlation-ID': uuid(),
611
+ },
612
+ signal,
613
+ }
614
+ );
615
+ return response.data.data;
616
+ },
617
+
618
+ dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
619
+ const response = await axios.delete(
620
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
621
+ {
622
+ headers: {
623
+ ...authHeaders,
624
+ 'X-Correlation-ID': uuid(),
625
+ },
626
+ }
627
+ );
628
+ return response.data.data;
629
+ },
630
+
631
+ restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
632
+ const response = await axios.post(
633
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
634
+ {},
635
+ {
636
+ headers: {
637
+ ...authHeaders,
638
+ 'X-Correlation-ID': uuid(),
639
+ },
640
+ }
641
+ );
642
+ return response.data.data;
643
+ },
644
+ };
645
+
646
+ function App() {
647
+ return (
648
+ <DismissibleProvider
649
+ userId="user-123"
650
+ baseUrl="https://api.yourapp.com"
651
+ client={axiosClient}
652
+ >
653
+ <YourApp />
654
+ </DismissibleProvider>
655
+ );
656
+ }
657
+ ```
617
658
 
618
- function AdminNotificationManager({ notificationId }) {
619
- const { dismissedOn, dismiss, restore, item, isLoading, error } =
620
- useDismissibleItem(notificationId);
659
+ #### Example: Custom Client with Logging
621
660
 
622
- if (error) {
623
- return <div className="error">Error: {error.message}</div>;
624
- }
661
+ ```tsx
662
+ import type { DismissibleClient } from '@dismissible/react-client';
663
+
664
+ const loggingClient: DismissibleClient = {
665
+ getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
666
+ console.log(`[Dismissible] Fetching item: ${itemId} for user: ${userId}`);
667
+ const startTime = performance.now();
668
+
669
+ const response = await fetch(
670
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
671
+ {
672
+ method: 'GET',
673
+ headers: authHeaders,
674
+ signal,
675
+ }
676
+ );
625
677
 
678
+ const data = await response.json();
679
+ console.log(`[Dismissible] Fetched in ${performance.now() - startTime}ms`);
680
+
681
+ if (!response.ok) {
682
+ throw new Error(data.message || 'Failed to fetch dismissible item');
683
+ }
684
+
685
+ return data.data;
686
+ },
687
+
688
+ dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
689
+ console.log(`[Dismissible] Dismissing item: ${itemId}`);
690
+
691
+ const response = await fetch(
692
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
693
+ {
694
+ method: 'DELETE',
695
+ headers: authHeaders,
696
+ }
697
+ );
698
+
699
+ const data = await response.json();
700
+
701
+ if (!response.ok) {
702
+ throw new Error(data.message || 'Failed to dismiss item');
703
+ }
704
+
705
+ console.log(`[Dismissible] Item dismissed at: ${data.data.dismissedAt}`);
706
+ return data.data;
707
+ },
708
+
709
+ restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
710
+ console.log(`[Dismissible] Restoring item: ${itemId}`);
711
+
712
+ const response = await fetch(
713
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
714
+ {
715
+ method: 'POST',
716
+ headers: authHeaders,
717
+ }
718
+ );
719
+
720
+ const data = await response.json();
721
+
722
+ if (!response.ok) {
723
+ throw new Error(data.message || 'Failed to restore item');
724
+ }
725
+
726
+ console.log(`[Dismissible] Item restored successfully`);
727
+ return data.data;
728
+ },
729
+ };
730
+
731
+ function App() {
626
732
  return (
627
- <div className="admin-panel">
628
- <h4>Notification: {notificationId}</h4>
629
- <p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
630
- {dismissedOn && (
631
- <p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
632
- )}
633
-
634
- <div className="actions">
635
- {dismissedOn ? (
636
- <button
637
- onClick={restore}
638
- disabled={isLoading}
639
- className="btn-restore"
640
- >
641
- Restore
642
- </button>
643
- ) : (
644
- <button
645
- onClick={dismiss}
646
- disabled={isLoading}
647
- className="btn-dismiss"
648
- >
649
- Dismiss
650
- </button>
651
- )}
652
- </div>
653
- </div>
733
+ <DismissibleProvider
734
+ userId="user-123"
735
+ baseUrl="https://api.yourapp.com"
736
+ client={loggingClient}
737
+ >
738
+ <YourApp />
739
+ </DismissibleProvider>
654
740
  );
655
741
  }
656
742
  ```
657
743
 
744
+ #### Example: Custom Client with Retry Logic
745
+
746
+ ```tsx
747
+ import type { DismissibleClient } from '@dismissible/react-client';
748
+
749
+ async function fetchWithRetry(
750
+ url: string,
751
+ options: RequestInit,
752
+ retries = 3
753
+ ): Promise<Response> {
754
+ for (let attempt = 1; attempt <= retries; attempt++) {
755
+ try {
756
+ const response = await fetch(url, options);
757
+ if (response.ok || attempt === retries) {
758
+ return response;
759
+ }
760
+ } catch (error) {
761
+ if (attempt === retries) throw error;
762
+ }
763
+ // Exponential backoff
764
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
765
+ }
766
+ throw new Error('Max retries reached');
767
+ }
768
+
769
+ const retryClient: DismissibleClient = {
770
+ getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
771
+ const response = await fetchWithRetry(
772
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
773
+ { method: 'GET', headers: authHeaders, signal }
774
+ );
775
+ const data = await response.json();
776
+ return data.data;
777
+ },
778
+
779
+ dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
780
+ const response = await fetchWithRetry(
781
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
782
+ { method: 'DELETE', headers: authHeaders }
783
+ );
784
+ const data = await response.json();
785
+ return data.data;
786
+ },
787
+
788
+ restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
789
+ const response = await fetchWithRetry(
790
+ `${baseUrl}/v1/users/${userId}/items/${itemId}`,
791
+ { method: 'POST', headers: authHeaders }
792
+ );
793
+ const data = await response.json();
794
+ return data.data;
795
+ },
796
+ };
797
+ ```
798
+
658
799
  ## Styling
659
800
 
660
801
  The library includes minimal default styles. You can override them or provide your own:
@@ -678,35 +819,6 @@ The library includes minimal default styles. You can override them or provide yo
678
819
  }
679
820
  ```
680
821
 
681
- ## TypeScript Support
682
-
683
- The library is written in TypeScript and exports all type definitions:
684
-
685
- ```tsx
686
- import type {
687
- DismissibleProps,
688
- DismissibleProviderProps,
689
- JwtToken,
690
- } from '@dismissible/react-client';
691
-
692
- // Custom provider wrapper
693
- const AuthenticatedDismissibleProvider: React.FC<{
694
- children: React.ReactNode;
695
- }> = ({ children }) => {
696
- const { user, getToken } = useAuth();
697
-
698
- return (
699
- <DismissibleProvider
700
- userId={user.id}
701
- jwt={getToken}
702
- baseUrl={process.env.DISMISSIBLE_API_URL}
703
- >
704
- {children}
705
- </DismissibleProvider>
706
- );
707
- };
708
- ```
709
-
710
822
  ## Self-Hosting
711
823
 
712
824
  Dismissible is designed to be self-hosted. You have full control over your data.
@@ -733,9 +845,9 @@ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup ins
733
845
 
734
846
  ## Support
735
847
 
736
- - 📖 [Documentation](https://dismissible.io/docs)
737
- - 🐙 [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
738
- - 🐙 [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
848
+ - [Documentation](https://dismissible.io/docs)
849
+ - [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
850
+ - [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
739
851
 
740
852
  ## License
741
853