@dismissible/react-client 2.0.0 → 2.1.0-canary.6.0a7a428

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 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
- | `baseUrl` | `string` | ❌ | API base URL (defaults to your self-hosted server) |
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>;