@terreno/ui 0.12.2 → 0.13.3

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.
Files changed (58) hide show
  1. package/dist/ConsentFormScreen.js +8 -7
  2. package/dist/ConsentFormScreen.js.map +1 -1
  3. package/dist/Field.js.map +1 -1
  4. package/dist/PickerSelect.d.ts +22 -10
  5. package/dist/PickerSelect.js +11 -9
  6. package/dist/PickerSelect.js.map +1 -1
  7. package/dist/SelectBadge.js +11 -1
  8. package/dist/SelectBadge.js.map +1 -1
  9. package/dist/SelectField.js +2 -2
  10. package/dist/SelectField.js.map +1 -1
  11. package/dist/SidebarNavigation.native.js.map +1 -1
  12. package/dist/Signature.js +4 -0
  13. package/dist/Signature.js.map +1 -1
  14. package/dist/Signature.native.js +4 -0
  15. package/dist/Signature.native.js.map +1 -1
  16. package/dist/Theme.d.ts +1 -1
  17. package/dist/Theme.js.map +1 -1
  18. package/dist/useConsentForms.d.ts +4 -3
  19. package/dist/useConsentForms.js +26 -4
  20. package/dist/useConsentForms.js.map +1 -1
  21. package/dist/useConsentHistory.d.ts +4 -3
  22. package/dist/useConsentHistory.js +26 -4
  23. package/dist/useConsentHistory.js.map +1 -1
  24. package/dist/useSubmitConsent.d.ts +7 -6
  25. package/dist/useSubmitConsent.js +25 -3
  26. package/dist/useSubmitConsent.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/ConsentFormScreen.test.tsx +91 -23
  29. package/src/ConsentFormScreen.tsx +21 -0
  30. package/src/Field.tsx +1 -1
  31. package/src/HeightField.test.tsx +68 -0
  32. package/src/Modal.tsx +2 -2
  33. package/src/NumberField.test.tsx +13 -19
  34. package/src/PickerSelect.tsx +48 -34
  35. package/src/SelectBadge.tsx +7 -3
  36. package/src/SelectField.tsx +2 -2
  37. package/src/SidebarNavigation.native.tsx +6 -2
  38. package/src/Signature.native.tsx +4 -0
  39. package/src/Signature.test.tsx +10 -0
  40. package/src/Signature.tsx +4 -0
  41. package/src/Theme.tsx +17 -14
  42. package/src/Tooltip.test.tsx +41 -22
  43. package/src/__snapshots__/AddressField.test.tsx.snap +0 -1
  44. package/src/__snapshots__/CustomSelectField.test.tsx.snap +0 -7
  45. package/src/__snapshots__/Field.test.tsx.snap +0 -6
  46. package/src/__snapshots__/PickerSelect.test.tsx.snap +0 -7
  47. package/src/__snapshots__/SelectField.test.tsx.snap +0 -6
  48. package/src/__snapshots__/TerrenoProvider.test.tsx.snap +4 -18
  49. package/src/__snapshots__/TimezonePicker.test.tsx.snap +0 -6
  50. package/src/bunSetup.ts +22 -0
  51. package/src/polyfill.d.ts +1 -1
  52. package/src/table/__snapshots__/TableBadge.test.tsx.snap +0 -1
  53. package/src/useConsentForms.test.ts +25 -0
  54. package/src/useConsentForms.ts +32 -7
  55. package/src/useConsentHistory.test.ts +99 -0
  56. package/src/useConsentHistory.ts +31 -6
  57. package/src/useSubmitConsent.test.ts +24 -0
  58. package/src/useSubmitConsent.ts +35 -10
@@ -51,7 +51,6 @@ exports[`SelectField renders correctly with default props 1`] = `
51
51
  },
52
52
  ],
53
53
  "props": {
54
- "activeOpacity": 1,
55
54
  "onPress": [Function],
56
55
  "style": {
57
56
  "alignItems": "center",
@@ -341,7 +340,6 @@ exports[`SelectField renders with title 1`] = `
341
340
  },
342
341
  ],
343
342
  "props": {
344
- "activeOpacity": 1,
345
343
  "onPress": [Function],
346
344
  "style": {
347
345
  "alignItems": "center",
@@ -616,7 +614,6 @@ exports[`SelectField renders with selected value 1`] = `
616
614
  },
617
615
  ],
618
616
  "props": {
619
- "activeOpacity": 1,
620
617
  "onPress": [Function],
621
618
  "style": {
622
619
  "alignItems": "center",
@@ -891,7 +888,6 @@ exports[`SelectField renders with custom placeholder 1`] = `
891
888
  },
892
889
  ],
893
890
  "props": {
894
- "activeOpacity": 1,
895
891
  "onPress": [Function],
896
892
  "style": {
897
893
  "alignItems": "center",
@@ -1166,7 +1162,6 @@ exports[`SelectField renders disabled state 1`] = `
1166
1162
  },
1167
1163
  ],
1168
1164
  "props": {
1169
- "activeOpacity": 1,
1170
1165
  "onPress": [Function],
1171
1166
  "style": {
1172
1167
  "alignItems": "center",
@@ -1443,7 +1438,6 @@ exports[`SelectField renders with requireValue (no clear option) 1`] = `
1443
1438
  },
1444
1439
  ],
1445
1440
  "props": {
1446
- "activeOpacity": 1,
1447
1441
  "onPress": [Function],
1448
1442
  "style": {
1449
1443
  "alignItems": "center",
@@ -27,15 +27,8 @@ exports[`TerrenoProvider renders correctly with default props 1`] = `
27
27
  },
28
28
  ],
29
29
  "props": {
30
- "collapsable": false,
31
- "pointerEvents": "box-none",
32
- "style": [
33
- {
34
- "flex": 1,
35
- },
36
- undefined,
37
- ],
38
- "testID": undefined,
30
+ "style": undefined,
31
+ "testID": "portal-host",
39
32
  },
40
33
  "type": "View",
41
34
  },
@@ -123,15 +116,8 @@ exports[`TerrenoProvider renders with openAPISpecUrl 1`] = `
123
116
  },
124
117
  ],
125
118
  "props": {
126
- "collapsable": false,
127
- "pointerEvents": "box-none",
128
- "style": [
129
- {
130
- "flex": 1,
131
- },
132
- undefined,
133
- ],
134
- "testID": undefined,
119
+ "style": undefined,
120
+ "testID": "portal-host",
135
121
  },
136
122
  "type": "View",
137
123
  },
@@ -66,7 +66,6 @@ exports[`TimezonePicker renders correctly with default props 1`] = `
66
66
  },
67
67
  ],
68
68
  "props": {
69
- "activeOpacity": 1,
70
69
  "onPress": [Function],
71
70
  "style": {
72
71
  "alignItems": "center",
@@ -381,7 +380,6 @@ exports[`TimezonePicker hides title when hideTitle is true 1`] = `
381
380
  },
382
381
  ],
383
382
  "props": {
384
- "activeOpacity": 1,
385
383
  "onPress": [Function],
386
384
  "style": {
387
385
  "alignItems": "center",
@@ -711,7 +709,6 @@ exports[`TimezonePicker renders with selected timezone 1`] = `
711
709
  },
712
710
  ],
713
711
  "props": {
714
- "activeOpacity": 1,
715
712
  "onPress": [Function],
716
713
  "style": {
717
714
  "alignItems": "center",
@@ -1041,7 +1038,6 @@ exports[`TimezonePicker renders USA timezones by default 1`] = `
1041
1038
  },
1042
1039
  ],
1043
1040
  "props": {
1044
- "activeOpacity": 1,
1045
1041
  "onPress": [Function],
1046
1042
  "style": {
1047
1043
  "alignItems": "center",
@@ -1371,7 +1367,6 @@ exports[`TimezonePicker renders with short timezone labels 1`] = `
1371
1367
  },
1372
1368
  ],
1373
1369
  "props": {
1374
- "activeOpacity": 1,
1375
1370
  "onPress": [Function],
1376
1371
  "style": {
1377
1372
  "alignItems": "center",
@@ -1701,7 +1696,6 @@ exports[`TimezonePicker calls onChange when timezone is selected 1`] = `
1701
1696
  },
1702
1697
  ],
1703
1698
  "props": {
1704
- "activeOpacity": 1,
1705
1699
  "onPress": [Function],
1706
1700
  "style": {
1707
1701
  "alignItems": "center",
package/src/bunSetup.ts CHANGED
@@ -530,6 +530,28 @@ mock.module("react-native-signature-canvas", () => ({
530
530
  Signature: mock(() => null),
531
531
  }));
532
532
 
533
+ // Mock react-signature-canvas (web). The real module references `window` at
534
+ // import time, which doesn't exist under bun test.
535
+ mock.module("react-signature-canvas", () => {
536
+ const SignatureCanvasMock = React.forwardRef(
537
+ ({backgroundColor}: {backgroundColor?: string}, _ref) =>
538
+ React.createElement("View", {style: {backgroundColor}, testID: "signature-canvas"})
539
+ );
540
+ return {__esModule: true, default: SignatureCanvasMock};
541
+ });
542
+
543
+ // Mock react-native-portalize. The real `Host` wraps children in an extra View
544
+ // whose presence makes snapshots brittle, and individual tests already mock
545
+ // this to render inline; hoisting the mock to setup keeps test ordering from
546
+ // leaking different shapes into other test files. Shape matches the per-file
547
+ // mock used by Tooltip.test.tsx so the two don't disagree.
548
+ mock.module("react-native-portalize", () => ({
549
+ Host: ({children}: MockComponentProps) =>
550
+ React.createElement("View", {style: undefined, testID: "portal-host"}, children),
551
+ Portal: ({children}: MockComponentProps) =>
552
+ React.createElement("View", {style: undefined, testID: "portal"}, children),
553
+ }));
554
+
533
555
  // Mock IconButton component
534
556
  mock.module("./IconButton", () => ({
535
557
  IconButton: mock(() => null),
package/src/polyfill.d.ts CHANGED
@@ -6,6 +6,6 @@
6
6
  // }
7
7
 
8
8
  // interface Object {
9
- // values<T>(obj: any): T[];
9
+ // values<T>(obj: object): T[];
10
10
  // }
11
11
  // }
@@ -485,7 +485,6 @@ exports[`TableBadge renders select field when editing 1`] = `
485
485
  },
486
486
  ],
487
487
  "props": {
488
- "activeOpacity": 1,
489
488
  "onPress": [Function],
490
489
  "style": {
491
490
  "alignItems": "center",
@@ -149,4 +149,29 @@ describe("useConsentForms", () => {
149
149
  const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
150
150
  expect(result.current.error).toBe("error");
151
151
  });
152
+
153
+ it("injects the pending-consents endpoint only once per (api, baseUrl)", () => {
154
+ let injectCallCount = 0;
155
+ const refetch = mock(() => {});
156
+ const useGetPendingConsentsQuery = mock(() => ({
157
+ data: undefined,
158
+ error: undefined,
159
+ isLoading: false,
160
+ refetch,
161
+ }));
162
+ const api = {
163
+ enhanceEndpoints: () => ({
164
+ injectEndpoints: () => {
165
+ injectCallCount += 1;
166
+ return {useGetPendingConsentsQuery};
167
+ },
168
+ }),
169
+ };
170
+ const {rerender} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
171
+ rerender(undefined);
172
+ rerender(undefined);
173
+ // The hook reuses the cached enhanced api after the first render so the
174
+ // dev-mode RTK warning about re-injecting endpoints never fires.
175
+ expect(injectCallCount).toBe(1);
176
+ });
152
177
  });
@@ -41,13 +41,15 @@ interface ConsentFormsHookState {
41
41
  refetch: () => void | Promise<void>;
42
42
  }
43
43
 
44
+ interface ConsentFormsEnhancedApi {
45
+ useGetPendingConsentsQuery: () => ConsentFormsHookState;
46
+ }
47
+
44
48
  interface ConsentFormsApiWithTags {
45
49
  injectEndpoints: (options: {
46
50
  endpoints: (build: ConsentFormsQueryBuilder) => {getPendingConsents: unknown};
47
51
  overrideExisting: boolean;
48
- }) => {
49
- useGetPendingConsentsQuery: () => ConsentFormsHookState;
50
- };
52
+ }) => ConsentFormsEnhancedApi;
51
53
  }
52
54
 
53
55
  interface ConsentFormsApi {
@@ -73,11 +75,27 @@ export const detectLocale = (): string => {
73
75
  return "en";
74
76
  };
75
77
 
76
- export const useConsentForms = (api: ConsentFormsApi, baseUrl?: string) => {
77
- const base = baseUrl || "";
78
- const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
78
+ /**
79
+ * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
80
+ * error in development whenever an endpoint with the same name is re-injected
81
+ * (with `overrideExisting: false`), so calling it on every render of every
82
+ * consumer would flood the console. WeakMap-by-api lets the GC reclaim entries
83
+ * when the api object is unreachable.
84
+ */
85
+ const enhancedApiCache = new WeakMap<ConsentFormsApi, Map<string, ConsentFormsEnhancedApi>>();
79
86
 
80
- const enhancedApi = apiWithConsentTags.injectEndpoints({
87
+ const getEnhancedApi = (api: ConsentFormsApi, base: string): ConsentFormsEnhancedApi => {
88
+ let byBase = enhancedApiCache.get(api);
89
+ if (!byBase) {
90
+ byBase = new Map();
91
+ enhancedApiCache.set(api, byBase);
92
+ }
93
+ const cached = byBase.get(base);
94
+ if (cached) {
95
+ return cached;
96
+ }
97
+ const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
98
+ const enhanced = apiWithConsentTags.injectEndpoints({
81
99
  endpoints: (build) => ({
82
100
  getPendingConsents: build.query({
83
101
  async onQueryStarted(_arg: unknown, {queryFulfilled}) {
@@ -97,6 +115,13 @@ export const useConsentForms = (api: ConsentFormsApi, baseUrl?: string) => {
97
115
  }),
98
116
  overrideExisting: false,
99
117
  });
118
+ byBase.set(base, enhanced);
119
+ return enhanced;
120
+ };
121
+
122
+ export const useConsentForms = (api: ConsentFormsApi, baseUrl?: string) => {
123
+ const base = baseUrl || "";
124
+ const enhancedApi = getEnhancedApi(api, base);
100
125
 
101
126
  const {data, isLoading, error, refetch} = enhancedApi.useGetPendingConsentsQuery();
102
127
  const forms: ConsentFormPublic[] = Array.isArray(data) ? data : (data?.data ?? []);
@@ -0,0 +1,99 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {renderHook} from "@testing-library/react-native";
3
+
4
+ import type {ConsentHistoryEntry} from "./useConsentHistory";
5
+ import {useConsentHistory} from "./useConsentHistory";
6
+
7
+ type ConsentHistoryApi = Parameters<typeof useConsentHistory>[0];
8
+
9
+ interface MockQueryDef {
10
+ query: () => string;
11
+ providesTags: string[];
12
+ }
13
+
14
+ interface MockInjectOpts {
15
+ endpoints: (build: {query: (def: MockQueryDef) => string}) => Record<string, unknown>;
16
+ }
17
+
18
+ describe("useConsentHistory", () => {
19
+ const buildApi = (queryResult: {data?: unknown; error?: unknown; isLoading?: boolean}) => {
20
+ const refetch = mock(() => {});
21
+ const useGetMyConsentsQuery = mock(() => ({
22
+ data: queryResult.data,
23
+ error: queryResult.error,
24
+ isLoading: queryResult.isLoading ?? false,
25
+ refetch,
26
+ }));
27
+ const api = {
28
+ injectEndpoints: mock((opts: MockInjectOpts) => {
29
+ const build = {
30
+ query: mock((def: MockQueryDef) => {
31
+ // Exercise the URL builder so the closure captures `base`
32
+ const url = def.query();
33
+ expect(url).toContain("/consents/my");
34
+ return "my-consents-query";
35
+ }),
36
+ };
37
+ opts.endpoints(build);
38
+ return {useGetMyConsentsQuery};
39
+ }),
40
+ };
41
+ return {api, refetch};
42
+ };
43
+
44
+ it("returns an array of entries when response is an array", () => {
45
+ const {api} = buildApi({
46
+ data: [{_id: "1", agreed: true} as unknown as ConsentHistoryEntry],
47
+ });
48
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
49
+ expect(Array.isArray(result.current.entries)).toBe(true);
50
+ expect(result.current.entries).toHaveLength(1);
51
+ });
52
+
53
+ it("unwraps .data property when response is object shape", () => {
54
+ const {api} = buildApi({
55
+ data: {data: [{_id: "2", agreed: false} as unknown as ConsentHistoryEntry]},
56
+ });
57
+ const {result} = renderHook(() =>
58
+ useConsentHistory(api as unknown as ConsentHistoryApi, "/api")
59
+ );
60
+ expect(Array.isArray(result.current.entries)).toBe(true);
61
+ expect(result.current.entries).toHaveLength(1);
62
+ });
63
+
64
+ it("returns empty array when no data is present", () => {
65
+ const {api} = buildApi({data: undefined, isLoading: true});
66
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
67
+ expect(result.current.entries).toEqual([]);
68
+ expect(result.current.isLoading).toBe(true);
69
+ });
70
+
71
+ it("surfaces error from the query hook", () => {
72
+ const {api} = buildApi({data: undefined, error: "boom"});
73
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
74
+ expect(result.current.error).toBe("boom");
75
+ });
76
+
77
+ it("injects the my-consents endpoint only once per (api, baseUrl)", () => {
78
+ let injectCallCount = 0;
79
+ const refetch = mock(() => {});
80
+ const useGetMyConsentsQuery = mock(() => ({
81
+ data: undefined,
82
+ error: undefined,
83
+ isLoading: false,
84
+ refetch,
85
+ }));
86
+ const api = {
87
+ injectEndpoints: () => {
88
+ injectCallCount += 1;
89
+ return {useGetMyConsentsQuery};
90
+ },
91
+ };
92
+ const {rerender} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
93
+ rerender(undefined);
94
+ rerender(undefined);
95
+ // The hook reuses the cached enhanced api after the first render so the
96
+ // dev-mode RTK warning about re-injecting endpoints never fires.
97
+ expect(injectCallCount).toBe(1);
98
+ });
99
+ });
@@ -35,19 +35,37 @@ interface ConsentHistoryHookState {
35
35
  refetch: () => void | Promise<void>;
36
36
  }
37
37
 
38
+ interface ConsentHistoryEnhancedApi {
39
+ useGetMyConsentsQuery: () => ConsentHistoryHookState;
40
+ }
41
+
38
42
  interface ConsentHistoryApi {
39
43
  injectEndpoints: (options: {
40
44
  endpoints: (build: ConsentHistoryQueryBuilder) => {getMyConsents: unknown};
41
45
  overrideExisting: boolean;
42
- }) => {
43
- useGetMyConsentsQuery: () => ConsentHistoryHookState;
44
- };
46
+ }) => ConsentHistoryEnhancedApi;
45
47
  }
46
48
 
47
- export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
48
- const base = baseUrl || "";
49
+ /**
50
+ * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
51
+ * error in development whenever an endpoint with the same name is re-injected
52
+ * (with `overrideExisting: false`), so calling it on every render of every
53
+ * consumer would flood the console. WeakMap-by-api lets the GC reclaim entries
54
+ * when the api object is unreachable.
55
+ */
56
+ const enhancedApiCache = new WeakMap<ConsentHistoryApi, Map<string, ConsentHistoryEnhancedApi>>();
49
57
 
50
- const enhancedApi = api.injectEndpoints({
58
+ const getEnhancedApi = (api: ConsentHistoryApi, base: string): ConsentHistoryEnhancedApi => {
59
+ let byBase = enhancedApiCache.get(api);
60
+ if (!byBase) {
61
+ byBase = new Map();
62
+ enhancedApiCache.set(api, byBase);
63
+ }
64
+ const cached = byBase.get(base);
65
+ if (cached) {
66
+ return cached;
67
+ }
68
+ const enhanced = api.injectEndpoints({
51
69
  endpoints: (build) => ({
52
70
  getMyConsents: build.query({
53
71
  providesTags: ["MyConsents"],
@@ -56,6 +74,13 @@ export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
56
74
  }),
57
75
  overrideExisting: false,
58
76
  });
77
+ byBase.set(base, enhanced);
78
+ return enhanced;
79
+ };
80
+
81
+ export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
82
+ const base = baseUrl || "";
83
+ const enhancedApi = getEnhancedApi(api, base);
59
84
 
60
85
  const {data, isLoading, error, refetch} = enhancedApi.useGetMyConsentsQuery();
61
86
  const entries: ConsentHistoryEntry[] = Array.isArray(data) ? data : (data?.data ?? []);
@@ -72,4 +72,28 @@ describe("useSubmitConsent", () => {
72
72
  const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
73
73
  expect(result.current.submit).toBeDefined();
74
74
  });
75
+
76
+ it("injects the submit endpoint only once per (api, baseUrl)", () => {
77
+ let injectCallCount = 0;
78
+ const unwrap = mock(async () => ({}));
79
+ const submitMutation = mock(() => ({unwrap}));
80
+ const useSubmitConsentResponseMutation = mock(() => [
81
+ submitMutation,
82
+ {error: undefined, isLoading: false},
83
+ ]);
84
+ const api = {
85
+ enhanceEndpoints: () => ({
86
+ injectEndpoints: () => {
87
+ injectCallCount += 1;
88
+ return {useSubmitConsentResponseMutation};
89
+ },
90
+ }),
91
+ };
92
+ const {rerender} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
93
+ rerender(undefined);
94
+ rerender(undefined);
95
+ // The hook reuses the cached enhanced api after the first render so the
96
+ // dev-mode RTK warning about re-injecting endpoints never fires.
97
+ expect(injectCallCount).toBe(1);
98
+ });
75
99
  });
@@ -26,27 +26,45 @@ interface SubmitConsentMutationBuilder {
26
26
  }) => unknown;
27
27
  }
28
28
 
29
+ interface SubmitConsentEnhancedApi {
30
+ useSubmitConsentResponseMutation: () => [
31
+ (body: SubmitConsentBody) => SubmitConsentMutationResult,
32
+ SubmitConsentMutationHookState,
33
+ ];
34
+ }
35
+
29
36
  interface SubmitConsentApiWithTags {
30
37
  injectEndpoints: (options: {
31
38
  endpoints: (build: SubmitConsentMutationBuilder) => {submitConsentResponse: unknown};
32
39
  overrideExisting: boolean;
33
- }) => {
34
- useSubmitConsentResponseMutation: () => [
35
- (body: SubmitConsentBody) => SubmitConsentMutationResult,
36
- SubmitConsentMutationHookState,
37
- ];
38
- };
40
+ }) => SubmitConsentEnhancedApi;
39
41
  }
40
42
 
41
43
  interface SubmitConsentApi {
42
44
  enhanceEndpoints: (options: {addTagTypes: string[]}) => SubmitConsentApiWithTags;
43
45
  }
44
46
 
45
- export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
46
- const base = baseUrl || "";
47
- const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
47
+ /**
48
+ * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
49
+ * error in development whenever an endpoint with the same name is re-injected
50
+ * (with `overrideExisting: false`), so calling it on every render of every
51
+ * consumer would flood the console. WeakMap-by-api lets the GC reclaim entries
52
+ * when the api object is unreachable.
53
+ */
54
+ const enhancedApiCache = new WeakMap<SubmitConsentApi, Map<string, SubmitConsentEnhancedApi>>();
48
55
 
49
- const enhancedApi = apiWithConsentTags.injectEndpoints({
56
+ const getEnhancedApi = (api: SubmitConsentApi, base: string): SubmitConsentEnhancedApi => {
57
+ let byBase = enhancedApiCache.get(api);
58
+ if (!byBase) {
59
+ byBase = new Map();
60
+ enhancedApiCache.set(api, byBase);
61
+ }
62
+ const cached = byBase.get(base);
63
+ if (cached) {
64
+ return cached;
65
+ }
66
+ const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
67
+ const enhanced = apiWithConsentTags.injectEndpoints({
50
68
  endpoints: (build) => ({
51
69
  submitConsentResponse: build.mutation({
52
70
  invalidatesTags: ["PendingConsents"],
@@ -59,6 +77,13 @@ export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
59
77
  }),
60
78
  overrideExisting: false,
61
79
  });
80
+ byBase.set(base, enhanced);
81
+ return enhanced;
82
+ };
83
+
84
+ export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
85
+ const base = baseUrl || "";
86
+ const enhancedApi = getEnhancedApi(api, base);
62
87
 
63
88
  const [submitMutation, {isLoading: isSubmitting, error}] =
64
89
  enhancedApi.useSubmitConsentResponseMutation();