@dismissible/react-client 2.1.0 → 3.0.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 +12 -0
- package/README.md +178 -0
- package/dist/dismissible-client.es.js +609 -471
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/root.d.ts +1 -0
- package/dist/types/dismissible.types.d.ts +32 -0
- package/dist/utils/BatchScheduler.d.ts +72 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [3.0.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v2.1.0...v3.0.0) (2026-01-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **batch:** support for the new batch endpoint ([#8](https://github.com/DismissibleIo/dismissible-react-client/issues/8)) ([748fa71](https://github.com/DismissibleIo/dismissible-react-client/commit/748fa71c4d3b1eafed694d06c478bebb8e9d2ea0))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
* **batch:** Requires the new batch method on the http client
|
|
12
|
+
|
|
1
13
|
# [2.1.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v2.0.0...v2.1.0) (2026-01-16)
|
|
2
14
|
|
|
3
15
|
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ This component is used with the [Dismissible API Server](https://github.com/Dism
|
|
|
25
25
|
|
|
26
26
|
- **Easy to use** - Simple component API for dismissible content
|
|
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
|
+
- **Automatic request batching** - Multiple items requested in the same render cycle are automatically coalesced into a single API call
|
|
28
29
|
- **Restore support** - Restore previously dismissed items programmatically
|
|
29
30
|
- **JWT Authentication** - Built-in support for secure JWT-based authentication
|
|
30
31
|
- **Custom HTTP Client** - Bring your own HTTP client (axios, ky, etc.) with custom headers, interceptors, and tracking
|
|
@@ -550,6 +551,115 @@ function RestorableBanner({ itemId }) {
|
|
|
550
551
|
|
|
551
552
|
## Advanced Usage
|
|
552
553
|
|
|
554
|
+
### Automatic Request Batching
|
|
555
|
+
|
|
556
|
+
The library automatically batches multiple dismissible item requests into a single API call, dramatically reducing network overhead when rendering pages with many dismissible components.
|
|
557
|
+
|
|
558
|
+
#### How It Works
|
|
559
|
+
|
|
560
|
+
Under the hood, Dismissible uses a `BatchScheduler` that implements [DataLoader](https://github.com/graphql/dataloader)-style request coalescing:
|
|
561
|
+
|
|
562
|
+
1. **Request Collection**: When multiple `<Dismissible>` components or `useDismissibleItem` hooks mount during the same render cycle, each request is queued rather than fired immediately.
|
|
563
|
+
|
|
564
|
+
2. **Microtask Scheduling**: The scheduler uses `queueMicrotask()` to defer execution until after all synchronous code in the current JavaScript tick completes.
|
|
565
|
+
|
|
566
|
+
3. **Batch Execution**: All queued requests are combined into a single batch API call (up to 50 items per batch, with automatic splitting for larger sets).
|
|
567
|
+
|
|
568
|
+
4. **Result Distribution**: When the API responds, results are distributed back to each waiting component.
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
572
|
+
│ Same JavaScript Tick │
|
|
573
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
574
|
+
│ Component A Component B Component C │
|
|
575
|
+
│ requests "banner" requests "modal" requests "tooltip" │
|
|
576
|
+
│ │ │ │ │
|
|
577
|
+
│ └────────────────────┼────────────────────┘ │
|
|
578
|
+
│ ▼ │
|
|
579
|
+
│ ┌───────────────┐ │
|
|
580
|
+
│ │ BatchScheduler│ │
|
|
581
|
+
│ │ Queue │ │
|
|
582
|
+
│ └───────┬───────┘ │
|
|
583
|
+
│ │ │
|
|
584
|
+
│ queueMicrotask │
|
|
585
|
+
└────────────────────────────┼────────────────────────────────────┘
|
|
586
|
+
▼
|
|
587
|
+
┌─────────────────────────┐
|
|
588
|
+
│ Single API Call │
|
|
589
|
+
│ POST /v1/users/{id}/ │
|
|
590
|
+
│ items/batch │
|
|
591
|
+
│ ["banner", "modal", │
|
|
592
|
+
│ "tooltip"] │
|
|
593
|
+
└─────────────────────────┘
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### Example: Dashboard with Multiple Dismissibles
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
// Without batching: 5 separate API calls
|
|
600
|
+
// With batching: 1 single API call containing all 5 item IDs
|
|
601
|
+
|
|
602
|
+
function Dashboard() {
|
|
603
|
+
return (
|
|
604
|
+
<div>
|
|
605
|
+
<Dismissible itemId="welcome-banner">
|
|
606
|
+
<WelcomeBanner />
|
|
607
|
+
</Dismissible>
|
|
608
|
+
|
|
609
|
+
<Dismissible itemId="feature-announcement">
|
|
610
|
+
<FeatureAnnouncement />
|
|
611
|
+
</Dismissible>
|
|
612
|
+
|
|
613
|
+
<Dismissible itemId="survey-prompt">
|
|
614
|
+
<SurveyPrompt />
|
|
615
|
+
</Dismissible>
|
|
616
|
+
|
|
617
|
+
<Dismissible itemId="upgrade-notice">
|
|
618
|
+
<UpgradeNotice />
|
|
619
|
+
</Dismissible>
|
|
620
|
+
|
|
621
|
+
<Dismissible itemId="maintenance-alert">
|
|
622
|
+
<MaintenanceAlert />
|
|
623
|
+
</Dismissible>
|
|
624
|
+
</div>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### Built-in Optimizations
|
|
630
|
+
|
|
631
|
+
The `BatchScheduler` includes several optimizations:
|
|
632
|
+
|
|
633
|
+
- **Request Deduplication**: If the same `itemId` is requested multiple times in the same tick, only one request is made and the result is shared.
|
|
634
|
+
- **In-Memory Caching**: Previously fetched items are cached in memory to avoid redundant API calls.
|
|
635
|
+
- **Cache Priming**: Items loaded from localStorage are automatically primed in the batch cache.
|
|
636
|
+
- **Cache Sync**: When items are dismissed or restored, the batch cache is updated to ensure consistency.
|
|
637
|
+
|
|
638
|
+
#### Using the Hook with Batching
|
|
639
|
+
|
|
640
|
+
The batching is completely transparent when using the `useDismissibleItem` hook:
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
function NotificationCenter() {
|
|
644
|
+
// All three hooks will batch their requests into a single API call
|
|
645
|
+
const notification1 = useDismissibleItem('notification-1');
|
|
646
|
+
const notification2 = useDismissibleItem('notification-2');
|
|
647
|
+
const notification3 = useDismissibleItem('notification-3');
|
|
648
|
+
|
|
649
|
+
// Rendering logic...
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
#### Performance Impact
|
|
654
|
+
|
|
655
|
+
| Scenario | Without Batching | With Batching |
|
|
656
|
+
|----------|------------------|---------------|
|
|
657
|
+
| 5 dismissible items | 5 HTTP requests | 1 HTTP request |
|
|
658
|
+
| 20 dismissible items | 20 HTTP requests | 1 HTTP request |
|
|
659
|
+
| 100 dismissible items | 100 HTTP requests | 2 HTTP requests* |
|
|
660
|
+
|
|
661
|
+
\* *Batches are automatically split at 50 items to respect API limits*
|
|
662
|
+
|
|
553
663
|
### Custom HTTP Client
|
|
554
664
|
|
|
555
665
|
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:
|
|
@@ -576,6 +686,15 @@ interface DismissibleClient {
|
|
|
576
686
|
signal?: AbortSignal;
|
|
577
687
|
}) => Promise<DismissibleItem>;
|
|
578
688
|
|
|
689
|
+
// Required for automatic batching - fetches multiple items in one API call
|
|
690
|
+
batchGetOrCreate: (params: {
|
|
691
|
+
userId: string;
|
|
692
|
+
itemIds: string[]; // Array of item IDs (max 50)
|
|
693
|
+
baseUrl: string;
|
|
694
|
+
authHeaders: { Authorization?: string };
|
|
695
|
+
signal?: AbortSignal;
|
|
696
|
+
}) => Promise<DismissibleItem[]>;
|
|
697
|
+
|
|
579
698
|
dismiss: (params: {
|
|
580
699
|
userId: string;
|
|
581
700
|
itemId: string;
|
|
@@ -592,6 +711,8 @@ interface DismissibleClient {
|
|
|
592
711
|
}
|
|
593
712
|
```
|
|
594
713
|
|
|
714
|
+
> **Note**: The `batchGetOrCreate` method is essential for the automatic request batching feature. When multiple components request items in the same render cycle, this method is called instead of multiple `getOrCreate` calls.
|
|
715
|
+
|
|
595
716
|
#### Example: Custom Client with Axios
|
|
596
717
|
|
|
597
718
|
```tsx
|
|
@@ -615,6 +736,22 @@ const axiosClient: DismissibleClient = {
|
|
|
615
736
|
return response.data.data;
|
|
616
737
|
},
|
|
617
738
|
|
|
739
|
+
// Batch endpoint for automatic request coalescing
|
|
740
|
+
batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
|
|
741
|
+
const response = await axios.post(
|
|
742
|
+
`${baseUrl}/v1/users/${userId}/items/batch`,
|
|
743
|
+
{ itemIds },
|
|
744
|
+
{
|
|
745
|
+
headers: {
|
|
746
|
+
...authHeaders,
|
|
747
|
+
'X-Correlation-ID': uuid(),
|
|
748
|
+
},
|
|
749
|
+
signal,
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
return response.data.data;
|
|
753
|
+
},
|
|
754
|
+
|
|
618
755
|
dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
|
|
619
756
|
const response = await axios.delete(
|
|
620
757
|
`${baseUrl}/v1/users/${userId}/items/${itemId}`,
|
|
@@ -685,6 +822,33 @@ const loggingClient: DismissibleClient = {
|
|
|
685
822
|
return data.data;
|
|
686
823
|
},
|
|
687
824
|
|
|
825
|
+
batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
|
|
826
|
+
console.log(`[Dismissible] Batch fetching ${itemIds.length} items for user: ${userId}`);
|
|
827
|
+
const startTime = performance.now();
|
|
828
|
+
|
|
829
|
+
const response = await fetch(
|
|
830
|
+
`${baseUrl}/v1/users/${userId}/items/batch`,
|
|
831
|
+
{
|
|
832
|
+
method: 'POST',
|
|
833
|
+
headers: {
|
|
834
|
+
...authHeaders,
|
|
835
|
+
'Content-Type': 'application/json',
|
|
836
|
+
},
|
|
837
|
+
body: JSON.stringify({ itemIds }),
|
|
838
|
+
signal,
|
|
839
|
+
}
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const data = await response.json();
|
|
843
|
+
console.log(`[Dismissible] Batch fetched ${itemIds.length} items in ${performance.now() - startTime}ms`);
|
|
844
|
+
|
|
845
|
+
if (!response.ok) {
|
|
846
|
+
throw new Error(data.message || 'Failed to batch fetch dismissible items');
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return data.data;
|
|
850
|
+
},
|
|
851
|
+
|
|
688
852
|
dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
|
|
689
853
|
console.log(`[Dismissible] Dismissing item: ${itemId}`);
|
|
690
854
|
|
|
@@ -776,6 +940,20 @@ const retryClient: DismissibleClient = {
|
|
|
776
940
|
return data.data;
|
|
777
941
|
},
|
|
778
942
|
|
|
943
|
+
batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
|
|
944
|
+
const response = await fetchWithRetry(
|
|
945
|
+
`${baseUrl}/v1/users/${userId}/items/batch`,
|
|
946
|
+
{
|
|
947
|
+
method: 'POST',
|
|
948
|
+
headers: { ...authHeaders, 'Content-Type': 'application/json' },
|
|
949
|
+
body: JSON.stringify({ itemIds }),
|
|
950
|
+
signal,
|
|
951
|
+
}
|
|
952
|
+
);
|
|
953
|
+
const data = await response.json();
|
|
954
|
+
return data.data;
|
|
955
|
+
},
|
|
956
|
+
|
|
779
957
|
dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
|
|
780
958
|
const response = await fetchWithRetry(
|
|
781
959
|
`${baseUrl}/v1/users/${userId}/items/${itemId}`,
|