@hubspot/ui-extensions 0.9.3 → 0.9.5

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.
@@ -0,0 +1,419 @@
1
+ import { renderHook, waitFor } from '@testing-library/react';
2
+ import { useAssociations } from '../../../experimental/hooks/useAssociations';
3
+ import { fetchAssociations } from '../../../experimental/crm/fetchAssociations';
4
+ // Mock the logger module
5
+ jest.mock('../../../logger', () => ({
6
+ logger: {
7
+ debug: jest.fn(),
8
+ info: jest.fn(),
9
+ warn: jest.fn(),
10
+ error: jest.fn(),
11
+ },
12
+ }));
13
+ // Mock the fetchAssociations function, keep utility functions
14
+ jest.mock('../../../experimental/crm/fetchAssociations', () => ({
15
+ ...jest.requireActual('../../../experimental/crm/fetchAssociations'),
16
+ fetchAssociations: jest.fn(),
17
+ }));
18
+ // Get reference to the mocked function
19
+ const mockFetchAssociations = fetchAssociations;
20
+ describe('useAssociations with Pagination', () => {
21
+ let originalError;
22
+ beforeAll(() => {
23
+ // Suppress React act() warnings coming from @testing-library/react
24
+ originalError = console.error;
25
+ console.error = (...args) => {
26
+ if (typeof args[0] === 'string' &&
27
+ (args[0].includes('ReactDOMTestUtils.act') ||
28
+ args[0].includes('was not wrapped in act')))
29
+ return;
30
+ originalError.call(console, ...args);
31
+ };
32
+ });
33
+ beforeEach(() => {
34
+ // Reset the mock before each test
35
+ mockFetchAssociations.mockReset();
36
+ });
37
+ afterAll(() => {
38
+ console.error = originalError;
39
+ });
40
+ describe('initial state', () => {
41
+ it('should initialize with proper pagination state', async () => {
42
+ mockFetchAssociations.mockResolvedValue({
43
+ data: {
44
+ results: [],
45
+ hasMore: false,
46
+ nextOffset: 0,
47
+ },
48
+ cleanup: jest.fn(),
49
+ });
50
+ const { result } = renderHook(() => useAssociations({
51
+ toObjectType: '0-1',
52
+ properties: ['firstname', 'lastname'],
53
+ pageLength: 10,
54
+ }));
55
+ await waitFor(() => {
56
+ expect(result.current.results).toEqual([]);
57
+ expect(result.current.error).toBeNull();
58
+ expect(result.current.isLoading).toBe(false);
59
+ expect(result.current.pagination.currentPage).toBe(1);
60
+ expect(result.current.pagination.pageSize).toBe(10);
61
+ expect(result.current.pagination.hasNextPage).toBe(false);
62
+ expect(result.current.pagination.hasPreviousPage).toBe(false);
63
+ });
64
+ });
65
+ it('should use default page size when not specified', async () => {
66
+ mockFetchAssociations.mockResolvedValue({
67
+ data: {
68
+ results: [],
69
+ hasMore: false,
70
+ nextOffset: 0,
71
+ },
72
+ cleanup: jest.fn(),
73
+ });
74
+ const { result } = renderHook(() => useAssociations({
75
+ toObjectType: '0-1',
76
+ properties: ['firstname'],
77
+ }));
78
+ await waitFor(() => {
79
+ expect(result.current.pagination.pageSize).toBe(10); // DEFAULT_PAGE_SIZE
80
+ });
81
+ });
82
+ });
83
+ describe('data fetching', () => {
84
+ it('should successfully fetch and return associations with pagination info', async () => {
85
+ mockFetchAssociations.mockResolvedValue({
86
+ data: {
87
+ results: [
88
+ {
89
+ toObjectId: 1001,
90
+ associationTypes: [
91
+ { category: 'HUBSPOT_DEFINED', typeId: 1, label: 'Primary' },
92
+ ],
93
+ properties: {
94
+ firstname: 'John',
95
+ lastname: 'Doe',
96
+ },
97
+ },
98
+ ],
99
+ hasMore: true,
100
+ nextOffset: 10,
101
+ },
102
+ cleanup: jest.fn(),
103
+ });
104
+ const { result } = renderHook(() => useAssociations({
105
+ toObjectType: '0-1',
106
+ properties: ['firstname', 'lastname'],
107
+ pageLength: 10,
108
+ }));
109
+ await waitFor(() => {
110
+ expect(result.current.results).toHaveLength(1);
111
+ expect(result.current.results[0].toObjectId).toBe(1001);
112
+ expect(result.current.pagination.hasNextPage).toBe(true);
113
+ expect(result.current.pagination.hasPreviousPage).toBe(false);
114
+ expect(result.current.pagination.currentPage).toBe(1);
115
+ expect(result.current.isLoading).toBe(false);
116
+ });
117
+ expect(mockFetchAssociations).toHaveBeenCalledWith(expect.objectContaining({
118
+ toObjectType: '0-1',
119
+ properties: ['firstname', 'lastname'],
120
+ pageLength: 10,
121
+ offset: 0,
122
+ }), expect.any(Object));
123
+ });
124
+ it('should handle empty results correctly', async () => {
125
+ mockFetchAssociations.mockResolvedValue({
126
+ data: {
127
+ results: [],
128
+ hasMore: false,
129
+ nextOffset: 0,
130
+ },
131
+ cleanup: jest.fn(),
132
+ });
133
+ const { result } = renderHook(() => useAssociations({
134
+ toObjectType: '0-1',
135
+ properties: ['firstname'],
136
+ pageLength: 5,
137
+ }));
138
+ await waitFor(() => {
139
+ expect(result.current.results).toEqual([]);
140
+ expect(result.current.pagination.hasNextPage).toBe(false);
141
+ expect(result.current.pagination.hasPreviousPage).toBe(false);
142
+ expect(result.current.isLoading).toBe(false);
143
+ expect(result.current.error).toBeNull();
144
+ });
145
+ });
146
+ });
147
+ describe('pagination actions', () => {
148
+ it('should navigate to next page correctly', async () => {
149
+ // First page response
150
+ mockFetchAssociations.mockResolvedValueOnce({
151
+ data: {
152
+ results: [{ toObjectId: 1, associationTypes: [], properties: {} }],
153
+ hasMore: true,
154
+ nextOffset: 10,
155
+ },
156
+ cleanup: jest.fn(),
157
+ });
158
+ // Second page response
159
+ mockFetchAssociations.mockResolvedValueOnce({
160
+ data: {
161
+ results: [{ toObjectId: 2, associationTypes: [], properties: {} }],
162
+ hasMore: false,
163
+ nextOffset: 20,
164
+ },
165
+ cleanup: jest.fn(),
166
+ });
167
+ const { result } = renderHook(() => useAssociations({
168
+ toObjectType: '0-1',
169
+ pageLength: 10,
170
+ }));
171
+ // Wait for initial load
172
+ await waitFor(() => {
173
+ expect(result.current.pagination.currentPage).toBe(1);
174
+ expect(result.current.pagination.hasNextPage).toBe(true);
175
+ });
176
+ // Navigate to next page
177
+ result.current.pagination.nextPage();
178
+ await waitFor(() => {
179
+ expect(result.current.pagination.currentPage).toBe(2);
180
+ expect(result.current.pagination.hasNextPage).toBe(false);
181
+ expect(result.current.pagination.hasPreviousPage).toBe(true);
182
+ });
183
+ // Verify API was called with correct offset for page 2
184
+ expect(mockFetchAssociations).toHaveBeenLastCalledWith(expect.objectContaining({
185
+ offset: 10, // (page 2 - 1) * pageSize 10 = 10
186
+ }), expect.any(Object));
187
+ });
188
+ it('should navigate to previous page correctly', async () => {
189
+ // First call: page 1 with hasMore: true (allows next page)
190
+ mockFetchAssociations.mockResolvedValueOnce({
191
+ data: {
192
+ results: [{ toObjectId: 1, associationTypes: [], properties: {} }],
193
+ hasMore: true,
194
+ nextOffset: 10,
195
+ },
196
+ cleanup: jest.fn(),
197
+ });
198
+ // Second call: page 2 with hasMore: false
199
+ mockFetchAssociations.mockResolvedValueOnce({
200
+ data: {
201
+ results: [{ toObjectId: 2, associationTypes: [], properties: {} }],
202
+ hasMore: false,
203
+ nextOffset: 20,
204
+ },
205
+ cleanup: jest.fn(),
206
+ });
207
+ // Third call: back to page 1
208
+ mockFetchAssociations.mockResolvedValueOnce({
209
+ data: {
210
+ results: [{ toObjectId: 1, associationTypes: [], properties: {} }],
211
+ hasMore: true,
212
+ nextOffset: 10,
213
+ },
214
+ cleanup: jest.fn(),
215
+ });
216
+ const { result } = renderHook(() => useAssociations({
217
+ toObjectType: '0-1',
218
+ pageLength: 10,
219
+ }));
220
+ // Wait for initial load (page 1)
221
+ await waitFor(() => {
222
+ expect(result.current.pagination.currentPage).toBe(1);
223
+ expect(result.current.pagination.hasNextPage).toBe(true);
224
+ });
225
+ // Go to page 2
226
+ result.current.pagination.nextPage();
227
+ await waitFor(() => {
228
+ expect(result.current.pagination.currentPage).toBe(2);
229
+ expect(result.current.pagination.hasPreviousPage).toBe(true);
230
+ });
231
+ // Go back to previous page
232
+ result.current.pagination.previousPage();
233
+ await waitFor(() => {
234
+ expect(result.current.pagination.currentPage).toBe(1);
235
+ expect(result.current.pagination.hasPreviousPage).toBe(false);
236
+ });
237
+ });
238
+ it('should reset to first page correctly', async () => {
239
+ // Initial page 1 - has more pages
240
+ mockFetchAssociations.mockResolvedValue({
241
+ data: {
242
+ results: [],
243
+ hasMore: true,
244
+ nextOffset: 10,
245
+ },
246
+ cleanup: jest.fn(),
247
+ });
248
+ const { result } = renderHook(() => useAssociations({
249
+ toObjectType: '0-1',
250
+ pageLength: 10,
251
+ }));
252
+ // Wait for initial load
253
+ await waitFor(() => {
254
+ expect(result.current.pagination.currentPage).toBe(1);
255
+ expect(result.current.pagination.hasNextPage).toBe(true);
256
+ });
257
+ // Navigate to page 2, then page 3
258
+ result.current.pagination.nextPage();
259
+ await waitFor(() => {
260
+ expect(result.current.pagination.currentPage).toBe(2);
261
+ });
262
+ result.current.pagination.nextPage();
263
+ await waitFor(() => {
264
+ expect(result.current.pagination.currentPage).toBe(3);
265
+ });
266
+ // Reset to first page
267
+ result.current.pagination.reset();
268
+ await waitFor(() => {
269
+ expect(result.current.pagination.currentPage).toBe(1);
270
+ expect(result.current.pagination.hasPreviousPage).toBe(false);
271
+ });
272
+ });
273
+ it('should not allow navigation beyond boundaries', async () => {
274
+ mockFetchAssociations.mockResolvedValue({
275
+ data: {
276
+ results: [],
277
+ hasMore: false,
278
+ nextOffset: 10,
279
+ },
280
+ cleanup: jest.fn(),
281
+ });
282
+ const { result } = renderHook(() => useAssociations({
283
+ toObjectType: '0-1',
284
+ pageLength: 10,
285
+ }));
286
+ await waitFor(() => {
287
+ expect(result.current.pagination.currentPage).toBe(1);
288
+ expect(result.current.pagination.hasNextPage).toBe(false);
289
+ });
290
+ // Try to go to next page when there isn't one
291
+ result.current.pagination.nextPage();
292
+ // Should stay on page 1
293
+ expect(result.current.pagination.currentPage).toBe(1);
294
+ // Try to go to previous page from page 1
295
+ result.current.pagination.previousPage();
296
+ // Should stay on page 1
297
+ expect(result.current.pagination.currentPage).toBe(1);
298
+ });
299
+ });
300
+ describe('error handling', () => {
301
+ it('should handle fetch errors correctly', async () => {
302
+ const errorMessage = 'Failed to fetch associations';
303
+ mockFetchAssociations.mockRejectedValue(new Error(errorMessage));
304
+ const { result } = renderHook(() => useAssociations({
305
+ toObjectType: '0-1',
306
+ properties: ['firstname'],
307
+ pageLength: 10,
308
+ }));
309
+ await waitFor(() => {
310
+ expect(result.current.error).toBeInstanceOf(Error);
311
+ expect(result.current.error?.message).toBe(errorMessage);
312
+ expect(result.current.results).toEqual([]);
313
+ expect(result.current.isLoading).toBe(false);
314
+ });
315
+ });
316
+ });
317
+ describe('options handling', () => {
318
+ it('should pass formatting options to fetchAssociations', async () => {
319
+ mockFetchAssociations.mockResolvedValue({
320
+ data: {
321
+ results: [],
322
+ hasMore: false,
323
+ nextOffset: 0,
324
+ },
325
+ cleanup: jest.fn(),
326
+ });
327
+ const config = {
328
+ toObjectType: '0-1',
329
+ properties: ['firstname', 'lastname'],
330
+ pageLength: 10,
331
+ };
332
+ const options = {
333
+ propertiesToFormat: ['firstname'],
334
+ formattingOptions: {
335
+ date: {
336
+ format: 'MM/DD/YYYY',
337
+ relative: true,
338
+ },
339
+ },
340
+ };
341
+ renderHook(() => useAssociations(config, options));
342
+ await waitFor(() => {
343
+ expect(mockFetchAssociations).toHaveBeenCalledWith(expect.objectContaining(config), options);
344
+ });
345
+ });
346
+ it('should use default empty options when no options provided', async () => {
347
+ mockFetchAssociations.mockResolvedValue({
348
+ data: {
349
+ results: [],
350
+ hasMore: false,
351
+ nextOffset: 0,
352
+ },
353
+ cleanup: jest.fn(),
354
+ });
355
+ const config = {
356
+ toObjectType: '0-1',
357
+ properties: ['firstname'],
358
+ pageLength: 10,
359
+ };
360
+ renderHook(() => useAssociations(config));
361
+ await waitFor(() => {
362
+ expect(mockFetchAssociations).toHaveBeenCalledWith(expect.objectContaining(config), {});
363
+ });
364
+ });
365
+ });
366
+ describe('lifecycle management', () => {
367
+ it('should call cleanup function on unmount', async () => {
368
+ const mockCleanup = jest.fn();
369
+ mockFetchAssociations.mockResolvedValue({
370
+ data: {
371
+ results: [],
372
+ hasMore: false,
373
+ nextOffset: 0,
374
+ },
375
+ cleanup: mockCleanup,
376
+ });
377
+ const config = {
378
+ toObjectType: '0-1',
379
+ properties: ['firstname'],
380
+ pageLength: 10,
381
+ };
382
+ const { unmount } = renderHook(() => useAssociations(config));
383
+ await waitFor(() => {
384
+ expect(mockFetchAssociations).toHaveBeenCalled();
385
+ });
386
+ unmount();
387
+ await waitFor(() => {
388
+ expect(mockCleanup).toHaveBeenCalled();
389
+ });
390
+ });
391
+ it('should handle stable reference optimization for config', async () => {
392
+ mockFetchAssociations.mockResolvedValue({
393
+ data: {
394
+ results: [],
395
+ hasMore: false,
396
+ nextOffset: 0,
397
+ },
398
+ cleanup: jest.fn(),
399
+ });
400
+ const config = {
401
+ toObjectType: '0-1',
402
+ properties: ['firstname'],
403
+ pageLength: 10,
404
+ };
405
+ const { rerender } = renderHook(({ configProp }) => useAssociations(configProp), {
406
+ initialProps: { configProp: config },
407
+ });
408
+ await waitFor(() => {
409
+ expect(mockFetchAssociations).toHaveBeenCalledTimes(1);
410
+ });
411
+ // Rerender with the same config object content but different reference
412
+ rerender({ configProp: { ...config } });
413
+ await waitFor(() => {
414
+ // Should not call fetchAssociations again due to stable reference optimization
415
+ expect(mockFetchAssociations).toHaveBeenCalledTimes(1);
416
+ });
417
+ });
418
+ });
419
+ });
@@ -794,3 +794,15 @@ export declare const SearchInput: "SearchInput" & {
794
794
  readonly props?: types.SearchInputProps | undefined;
795
795
  readonly children?: true | undefined;
796
796
  } & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"SearchInput", types.SearchInputProps, true>>;
797
+ /**
798
+ * The `TimeInput` component renders an input field where a user can select a time. Commonly used within the `Form` component.
799
+ *
800
+ * **Links:**
801
+ *
802
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/time-input Docs}
803
+ */
804
+ export declare const TimeInput: "TimeInput" & {
805
+ readonly type?: "TimeInput" | undefined;
806
+ readonly props?: types.TimeInputProps | undefined;
807
+ readonly children?: true | undefined;
808
+ } & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"TimeInput", types.TimeInputProps, true>>;
@@ -534,3 +534,11 @@ export const Tooltip = createRemoteReactComponent('Tooltip');
534
534
  * - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/search-input SearchInput Docs}
535
535
  */
536
536
  export const SearchInput = createRemoteReactComponent('SearchInput');
537
+ /**
538
+ * The `TimeInput` component renders an input field where a user can select a time. Commonly used within the `Form` component.
539
+ *
540
+ * **Links:**
541
+ *
542
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/time-input Docs}
543
+ */
544
+ export const TimeInput = createRemoteReactComponent('TimeInput');
@@ -0,0 +1,45 @@
1
+ import { FetchCrmPropertiesOptions } from './fetchCrmProperties';
2
+ export declare const DEFAULT_PAGE_SIZE = 10;
3
+ /**
4
+ * Convert page number and page size to offset for API calls
5
+ */
6
+ export declare function pageToOffset(page: number, pageSize: number): number;
7
+ /**
8
+ * Convert offset and page size to page number
9
+ */
10
+ export declare function offsetToPage(offset: number, pageSize: number): number;
11
+ /**
12
+ * Calculate pagination flags based on current page and API hasMore flag
13
+ */
14
+ export declare function calculatePaginationFlags(currentPage: number, hasMore: boolean): {
15
+ hasNextPage: boolean;
16
+ hasPreviousPage: boolean;
17
+ };
18
+ export type AssociationResult = {
19
+ toObjectId: number;
20
+ associationTypes: {
21
+ category: string;
22
+ typeId: number;
23
+ label: string;
24
+ }[];
25
+ properties: {
26
+ [key: string]: string | null;
27
+ };
28
+ };
29
+ export type AssociationsResponse = {
30
+ results: AssociationResult[];
31
+ hasMore: boolean;
32
+ nextOffset: number;
33
+ };
34
+ export type FetchAssociationsRequest = {
35
+ toObjectType: string;
36
+ properties?: string[];
37
+ pageLength?: number;
38
+ offset?: number;
39
+ };
40
+ export interface FetchAssociationsResult {
41
+ data: AssociationsResponse;
42
+ error?: string;
43
+ cleanup: () => void;
44
+ }
45
+ export declare const fetchAssociations: (request: FetchAssociationsRequest, options?: FetchCrmPropertiesOptions) => Promise<FetchAssociationsResult>;
@@ -0,0 +1,64 @@
1
+ export const DEFAULT_PAGE_SIZE = 10;
2
+ /**
3
+ * Convert page number and page size to offset for API calls
4
+ */
5
+ export function pageToOffset(page, pageSize) {
6
+ return (page - 1) * pageSize;
7
+ }
8
+ /**
9
+ * Convert offset and page size to page number
10
+ */
11
+ export function offsetToPage(offset, pageSize) {
12
+ return Math.floor(offset / pageSize) + 1;
13
+ }
14
+ /**
15
+ * Calculate pagination flags based on current page and API hasMore flag
16
+ */
17
+ export function calculatePaginationFlags(currentPage, hasMore) {
18
+ return {
19
+ hasNextPage: hasMore,
20
+ hasPreviousPage: currentPage > 1,
21
+ };
22
+ }
23
+ function isAssociationsResponse(data) {
24
+ if (data === null ||
25
+ typeof data !== 'object' ||
26
+ !Array.isArray(data.results) ||
27
+ typeof data.hasMore !== 'boolean' ||
28
+ typeof data.nextOffset !== 'number') {
29
+ return false;
30
+ }
31
+ return data.results.every((result) => result !== null &&
32
+ typeof result === 'object' &&
33
+ typeof result.toObjectId === 'number' &&
34
+ Array.isArray(result.associationTypes) &&
35
+ result.properties !== null &&
36
+ typeof result.properties === 'object');
37
+ }
38
+ export const fetchAssociations = async (request, options) => {
39
+ let response;
40
+ let result;
41
+ try {
42
+ // eslint-disable-next-line hubspot-dev/no-confusing-browser-globals
43
+ response = await self.fetchAssociations(request, options);
44
+ result = await response.json();
45
+ }
46
+ catch (error) {
47
+ throw error instanceof Error
48
+ ? error
49
+ : new Error('Failed to fetch associations: Unknown error');
50
+ }
51
+ if (result.error) {
52
+ throw new Error(result.error);
53
+ }
54
+ if (!response.ok) {
55
+ throw new Error(`Failed to fetch associations: ${response.statusText}`);
56
+ }
57
+ if (!isAssociationsResponse(result.data)) {
58
+ throw new Error('Invalid response format');
59
+ }
60
+ return {
61
+ data: result.data,
62
+ cleanup: result.cleanup || (() => { }),
63
+ };
64
+ };
@@ -19,6 +19,7 @@ export type FetchCrmPropertiesOptions = {
19
19
  };
20
20
  export interface FetchCrmPropertiesResult {
21
21
  data: Record<string, string | null>;
22
+ error?: string;
22
23
  cleanup: () => void;
23
24
  }
24
25
  export declare const fetchCrmProperties: (propertyNames: string[], propertiesUpdatedCallback: (properties: Record<string, string | null>) => void, options?: FetchCrmPropertiesOptions) => Promise<FetchCrmPropertiesResult>;
@@ -12,22 +12,27 @@ function isCrmPropertiesResponse(data) {
12
12
  return true;
13
13
  }
14
14
  export const fetchCrmProperties = async (propertyNames, propertiesUpdatedCallback, options) => {
15
+ let response;
16
+ let result;
15
17
  try {
16
18
  // eslint-disable-next-line hubspot-dev/no-confusing-browser-globals
17
- const response = await self.fetchCrmProperties(propertyNames, propertiesUpdatedCallback, options);
18
- if (!response.ok) {
19
- throw new Error(`Failed to fetch CRM properties: ${response.statusText}`);
20
- }
21
- const result = await response.json();
22
- if (!isCrmPropertiesResponse(result.data)) {
23
- throw new Error('Invalid response format');
24
- }
25
- return result;
19
+ response = await self.fetchCrmProperties(propertyNames, propertiesUpdatedCallback, options);
20
+ result = await response.json();
26
21
  }
27
22
  catch (error) {
28
- if (error instanceof Error) {
29
- throw error;
30
- }
31
- throw new Error('Failed to fetch CRM properties: Unknown error');
23
+ // Only handle network/parsing errors, not our validation errors
24
+ throw error instanceof Error
25
+ ? error
26
+ : new Error('Failed to fetch CRM properties: Unknown error');
32
27
  }
28
+ if (result.error) {
29
+ throw new Error(result.error);
30
+ }
31
+ if (!response.ok) {
32
+ throw new Error(`Failed to fetch CRM properties: ${response.statusText}`);
33
+ }
34
+ if (!isCrmPropertiesResponse(result.data)) {
35
+ throw new Error('Invalid response format');
36
+ }
37
+ return result;
33
38
  };
@@ -0,0 +1,22 @@
1
+ import { type FetchAssociationsRequest, type AssociationResult } from '../crm/fetchAssociations';
2
+ import { type FetchCrmPropertiesOptions } from '../crm/fetchCrmProperties';
3
+ export interface UseAssociationsOptions {
4
+ propertiesToFormat?: 'all' | string[];
5
+ formattingOptions?: FetchCrmPropertiesOptions['formattingOptions'];
6
+ }
7
+ export interface UseAssociationsPagination {
8
+ hasNextPage: boolean;
9
+ hasPreviousPage: boolean;
10
+ currentPage: number;
11
+ pageSize: number;
12
+ nextPage: () => void;
13
+ previousPage: () => void;
14
+ reset: () => void;
15
+ }
16
+ export interface UseAssociationsResult {
17
+ results: AssociationResult[];
18
+ error: Error | null;
19
+ isLoading: boolean;
20
+ pagination: UseAssociationsPagination;
21
+ }
22
+ export declare function useAssociations(config: Omit<FetchAssociationsRequest, 'offset'>, options?: UseAssociationsOptions): UseAssociationsResult;