@dismissible/react-client 2.0.0 → 2.1.0
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 +7 -0
- package/README.md +253 -1
- package/dist/clients/defaultClient.d.ts +25 -0
- package/dist/contexts/DismissibleProvider.d.ts +22 -0
- package/dist/dismissible-client.es.js +535 -494
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +1 -3
- package/dist/root.d.ts +1 -0
- package/dist/types/dismissible.types.d.ts +81 -7
- package/dist/utils/url.utils.d.ts +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.1.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v2.0.0...v2.1.0) (2026-01-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **fetch:** custom fetch client can be used via the provider ([#7](https://github.com/DismissibleIo/dismissible-react-client/issues/7)) ([1083205](https://github.com/DismissibleIo/dismissible-react-client/commit/108320575c980d7bd51f2185e22f4fc562db4cfe))
|
|
7
|
+
|
|
1
8
|
# [2.0.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v1.0.0...v2.0.0) (2026-01-06)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ This component is used with the [Dismissible API Server](https://github.com/Dism
|
|
|
27
27
|
- **Persistent state** - Dismissal state is saved and restored across sessions when using the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
28
28
|
- **Restore support** - Restore previously dismissed items programmatically
|
|
29
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
|
|
30
31
|
- **Customizable** - Custom loading, error, and dismiss button components
|
|
31
32
|
- **Accessible** - Built with accessibility best practices
|
|
32
33
|
- **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
|
|
@@ -124,8 +125,9 @@ Context provider that configures authentication and API settings. **Required** -
|
|
|
124
125
|
| Prop | Type | Required | Description |
|
|
125
126
|
|------|------|----------|-------------|
|
|
126
127
|
| `userId` | `string` | ✅ | User ID for tracking dismissals per user |
|
|
128
|
+
| `baseUrl` | `string` | ✅ | API base URL for your self-hosted server |
|
|
127
129
|
| `jwt` | `string \| (() => string) \| (() => Promise<string>)` | ❌ | JWT token for secure authentication |
|
|
128
|
-
| `
|
|
130
|
+
| `client` | `DismissibleClient` | ❌ | Custom HTTP client implementation (uses default if not provided) |
|
|
129
131
|
| `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
|
|
130
132
|
|
|
131
133
|
#### Example
|
|
@@ -186,6 +188,8 @@ function AppWithAsyncAuth() {
|
|
|
186
188
|
}
|
|
187
189
|
```
|
|
188
190
|
|
|
191
|
+
See [Custom HTTP Client](#custom-http-client) for examples of using a custom client.
|
|
192
|
+
|
|
189
193
|
### `<Dismissible>` Component
|
|
190
194
|
|
|
191
195
|
The main component for creating dismissible content.
|
|
@@ -544,6 +548,254 @@ function RestorableBanner({ itemId }) {
|
|
|
544
548
|
}
|
|
545
549
|
```
|
|
546
550
|
|
|
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
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
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
|
+
```
|
|
658
|
+
|
|
659
|
+
#### Example: Custom Client with Logging
|
|
660
|
+
|
|
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
|
+
);
|
|
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() {
|
|
732
|
+
return (
|
|
733
|
+
<DismissibleProvider
|
|
734
|
+
userId="user-123"
|
|
735
|
+
baseUrl="https://api.yourapp.com"
|
|
736
|
+
client={loggingClient}
|
|
737
|
+
>
|
|
738
|
+
<YourApp />
|
|
739
|
+
</DismissibleProvider>
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
```
|
|
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
|
+
|
|
547
799
|
## Styling
|
|
548
800
|
|
|
549
801
|
The library includes minimal default styles. You can override them or provide your own:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DismissibleClient } from '../types/dismissible.types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the default HTTP client using openapi-fetch
|
|
4
|
+
*
|
|
5
|
+
* @param baseUrl - The base URL for the API
|
|
6
|
+
* @returns A DismissibleClient implementation
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Use the default client directly
|
|
11
|
+
* const client = createDefaultClient("https://api.dismissible.io");
|
|
12
|
+
*
|
|
13
|
+
* // Or wrap it with custom behavior
|
|
14
|
+
* const customClient: DismissibleClient = {
|
|
15
|
+
* getOrCreate: async (params) => {
|
|
16
|
+
* console.log("Fetching item:", params.itemId);
|
|
17
|
+
* const defaultClient = createDefaultClient(params.baseUrl);
|
|
18
|
+
* return defaultClient.getOrCreate(params);
|
|
19
|
+
* },
|
|
20
|
+
* dismiss: async (params) => { ... },
|
|
21
|
+
* restore: async (params) => { ... },
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const createDefaultClient: (baseUrl: string) => DismissibleClient;
|
|
@@ -27,6 +27,28 @@ import { DismissibleProviderProps } from '../types/dismissible.types';
|
|
|
27
27
|
* >
|
|
28
28
|
* <App />
|
|
29
29
|
* </DismissibleProvider>
|
|
30
|
+
*
|
|
31
|
+
* // With custom HTTP client
|
|
32
|
+
* const myClient: DismissibleClient = {
|
|
33
|
+
* getOrCreate: async ({ resolvedPath, baseUrl, authHeaders, signal }) => {
|
|
34
|
+
* const res = await axios.get(`${baseUrl}${resolvedPath}`, {
|
|
35
|
+
* headers: { ...authHeaders, 'X-Correlation-ID': uuid() },
|
|
36
|
+
* signal
|
|
37
|
+
* });
|
|
38
|
+
* return res.data.data;
|
|
39
|
+
* },
|
|
40
|
+
* dismiss: async (params) => { ... },
|
|
41
|
+
* restore: async (params) => { ... },
|
|
42
|
+
* };
|
|
43
|
+
*
|
|
44
|
+
* <DismissibleProvider
|
|
45
|
+
* userId="user-123"
|
|
46
|
+
* jwt={token}
|
|
47
|
+
* baseUrl="https://api.dismissible.io"
|
|
48
|
+
* client={myClient}
|
|
49
|
+
* >
|
|
50
|
+
* <App />
|
|
51
|
+
* </DismissibleProvider>
|
|
30
52
|
* ```
|
|
31
53
|
*/
|
|
32
54
|
export declare const DismissibleProvider: React.FC<DismissibleProviderProps>;
|