Skip to content

Commit

Permalink
(feat) - O3-3189 - add hook to handle server-side pagination (#1114)
Browse files Browse the repository at this point in the history
* (feat) - O3-3189 - add hook to handle server-side pagination

* add mocks

* address PR comments
  • Loading branch information
chibongho authored Aug 16, 2024
1 parent 342781b commit 45bdc9b
Show file tree
Hide file tree
Showing 11 changed files with 742 additions and 2 deletions.
123 changes: 122 additions & 1 deletion packages/framework/esm-framework/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@
- [useOnClickOutside](API.md#useonclickoutside)
- [usePagination](API.md#usepagination)
- [usePatientPhoto](API.md#usepatientphoto)
- [useServerInfinite](API.md#useserverinfinite)
- [useServerPagination](API.md#useserverpagination)

### Utility Functions

Expand Down Expand Up @@ -6574,6 +6576,13 @@ ___

**usePagination**<`T`\>(`data?`, `resultsPerPage?`): `Object`

Use this hook to paginate data that already exists on the client side.
Note that if the data is obtained from server-side, the caller must handle server-side pagination manually.

**`see`** `useServerPagination` for hook that automatically manages server-side pagination.

**`see`** `useServerInfinite` for hook to get all data loaded onto the client-side

#### Type parameters

| Name |
Expand Down Expand Up @@ -6605,7 +6614,7 @@ ___

#### Defined in

[packages/framework/esm-react-utils/src/usePagination.ts:6](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/usePagination.ts#L6)
[packages/framework/esm-react-utils/src/usePagination.ts:15](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/usePagination.ts#L15)

___

Expand All @@ -6629,6 +6638,118 @@ ___

___

### useServerInfinite

**useServerInfinite**<`T`\>(`url`, `fetcher?`): [`UseServerInfiniteReturnObject`](interfaces/UseServerInfiniteReturnObject.md)<`T`\>

Most REST endpoints that return a list of objects, such as getAll or search, are server-side paginated.
The server limits the max number of results being returned, and multiple requests are needed to get the full data set
if its size exceed this limit.
The max number of results per request is configurable server-side
with the key "webservices.rest.maxResultsDefault". See: https://openmrs.atlassian.net/wiki/spaces/docs/pages/25469882/REST+Module

This hook fetches data from a paginated rest endpoint, initially by fetching the first page of the results.
It provides a callback to load data from subsequent pages as needed. This hook is intended to serve UIs that
provide infinite loading / scrolling of results.

While not ideal, this hook can be used to fetch the complete data set of results (from all pages) as follows:

useEffect(() => hasMore && loadMore(), [hasMore])

The above should only be used when there is a need to fetch the complete data set onto the client side (ex:
need to support client-side sorting or filtering of data).

**`see`** `useServerPagination` for lazy-loading paginated data`

#### Type parameters

| Name |
| :------ |
| `T` |

#### Parameters

| Name | Type | Default value | Description |
| :------ | :------ | :------ | :------ |
| `url` | `string` \| `URL` | `undefined` | The URL of the paginated rest endpoint. Note that the `limit` GET param can be set to specify the page size; if not set, the page size defaults to the `webservices.rest.maxResultsDefault` value defined server-side. |
| `fetcher` | (`key`: `string`) => `Promise`<[`FetchResponse`](interfaces/FetchResponse.md)<[`OpenMRSPaginatedResponse`](interfaces/OpenMRSPaginatedResponse.md)<`T`\>\>\> | `openmrsFetch` | The fetcher to use. Defaults to openmrsFetch |

#### Returns

[`UseServerInfiniteReturnObject`](interfaces/UseServerInfiniteReturnObject.md)<`T`\>

a UseServerInfiniteReturnObject object

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:77](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L77)

___

### useServerPagination

**useServerPagination**<`T`\>(`url`, `pageSize`, `fetcher?`): `Object`

Most REST endpoints that return a list of objects, such as getAll or search, are server-side paginated.
The server limits the max number of results being returned, and multiple requests are needed to get the full data set
if its size exceed this limit.
The max number of results per request is configurable server-side
with the key "webservices.rest.maxResultsDefault". See: https://openmrs.atlassian.net/wiki/spaces/docs/pages/25469882/REST+Module

For any UI that displays a paginated view of the full data set, we MUST handle the server-side pagination properly,
or else the UI does not correctly display the full data set.
This hook does that by providing callback functions for navigating to different pages of the results, and
lazy-loads the data on each page as needed.

Note that this hook is not suitable for use for situations that require client-side sorting or filtering
of the data set. In that case, all data must be loaded onto client-side first.

**`see`** `useServerInfinite` for completely loading data (from all pages) onto client side

**`see`** `usePagination` for pagination of client-side data`

#### Type parameters

| Name |
| :------ |
| `T` |

#### Parameters

| Name | Type | Default value | Description |
| :------ | :------ | :------ | :------ |
| `url` | `string` \| `URL` | `undefined` | The URL of the paginated rest endpoint. It should be populated with any needed GET params, except `limit`, `startIndex` or `totalCount`, which will be overridden and manipulated by the `goTo*` callbacks |
| `pageSize` | `number` | `undefined` | The number of results to return per page / fetch. Note that this value MUST NOT exceed "webservices.rest.maxResultsAbsolute", which should be reasonably high by default (1000). |
| `fetcher` | (`key`: `string`) => `Promise`<[`FetchResponse`](interfaces/FetchResponse.md)<[`OpenMRSPaginatedResponse`](interfaces/OpenMRSPaginatedResponse.md)<`T`\>\>\> | `openmrsFetch` | The fetcher to use. Defaults to openmrsFetch |

#### Returns

`Object`

| Name | Type | Description |
| :------ | :------ | :------ |
| `currentPage` | `number` | - |
| `currentPageSize` | `MutableRefObject`<`number`\> | - |
| `data` | `undefined` \| `T`[] | |
| `error` | `any` | The error object thrown by the fetcher function. |
| `goTo` | (`page`: `number`) => `void` | - |
| `goToNext` | () => `void` | - |
| `goToPrevious` | () => `void` | - |
| `isLoading` | `boolean` | - |
| `isValidating` | `boolean` | - |
| `mutate` | `KeyedMutator`<[`FetchResponse`](interfaces/FetchResponse.md)<[`OpenMRSPaginatedResponse`](interfaces/OpenMRSPaginatedResponse.md)<`T`\>\>\> | - |
| `paginated` | `boolean` | |
| `showNextButton` | `boolean` | |
| `showPreviousButton` | `boolean` | |
| `totalCount` | `number` | |
| `totalPages` | `number` | - |

#### Defined in

[packages/framework/esm-react-utils/src/useServerPagination.ts:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerPagination.ts#L38)

___

## Utility Functions

### age
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[@openmrs/esm-framework](../API.md) / OpenMRSPaginatedResponse

# Interface: OpenMRSPaginatedResponse<T\>

## Type parameters

| Name |
| :------ |
| `T` |

## Table of contents

### UI Properties

- [links](OpenMRSPaginatedResponse.md#links)
- [results](OpenMRSPaginatedResponse.md#results)
- [totalCount](OpenMRSPaginatedResponse.md#totalcount)

## UI Properties

### links

**links**: { `rel`: ``"prev"`` \| ``"next"`` ; `uri`: `string` }[]

#### Defined in

[packages/framework/esm-react-utils/src/useServerPagination.ts:8](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerPagination.ts#L8)

___

### results

**results**: `T`[]

#### Defined in

[packages/framework/esm-react-utils/src/useServerPagination.ts:7](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerPagination.ts#L7)

___

### totalCount

**totalCount**: `number`

#### Defined in

[packages/framework/esm-react-utils/src/useServerPagination.ts:9](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerPagination.ts#L9)
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
[@openmrs/esm-framework](../API.md) / UseServerInfiniteReturnObject

# Interface: UseServerInfiniteReturnObject<T\>

## Type parameters

| Name |
| :------ |
| `T` |

## Table of contents

### UI Properties

- [data](UseServerInfiniteReturnObject.md#data)
- [error](UseServerInfiniteReturnObject.md#error)
- [hasMore](UseServerInfiniteReturnObject.md#hasmore)
- [isLoading](UseServerInfiniteReturnObject.md#isloading)
- [isValidating](UseServerInfiniteReturnObject.md#isvalidating)
- [mutate](UseServerInfiniteReturnObject.md#mutate)
- [totalCount](UseServerInfiniteReturnObject.md#totalcount)

### UI Methods

- [loadMore](UseServerInfiniteReturnObject.md#loadmore)

## UI Properties

### data

**data**: `undefined` \| `T`[]

The data fetched from the server so far. Note that this array contains
the aggregate of data from all fetched pages. Unless hasMore == false,
this array does not contain the complete data set.

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:13](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L13)

___

### error

**error**: `any`

from useSWRInfinite

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:33](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L33)

___

### hasMore

**hasMore**: `boolean`

Whether there are more results in the data set that have not been fetched yet.

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:23](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L23)

___

### isLoading

**isLoading**: `boolean`

from useSWRInfinite

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:48](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L48)

___

### isValidating

**isValidating**: `boolean`

from useSWRInfinite

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:43](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L43)

___

### mutate

**mutate**: `KeyedMutator`<[`FetchResponse`](FetchResponse.md)<[`OpenMRSPaginatedResponse`](OpenMRSPaginatedResponse.md)<`T`\>\>[]\>

from useSWRInfinite

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L38)

___

### totalCount

**totalCount**: `undefined` \| `number`

The total number of rows in the data set.

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:18](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L18)

## UI Methods

### loadMore

**loadMore**(): `void`

callback function to make another fetch of next page's data set.

#### Returns

`void`

#### Defined in

[packages/framework/esm-react-utils/src/useServerInfinite.ts:28](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useServerInfinite.ts#L28)
25 changes: 24 additions & 1 deletion packages/framework/esm-react-utils/mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { openmrsFetch } from '@openmrs/esm-api/mock';
import { configSchema } from '@openmrs/esm-config/mock';
import { getExtensionStore, getExtensionInternalStore } from '@openmrs/esm-extensions/mock';
import { createGlobalStore } from '@openmrs/esm-state/mock';
import { isDesktop as realIsDesktop, usePagination as realUsePagination } from './src/index';
import {
isDesktop as realIsDesktop,
usePagination as realUsePagination,
useServerPagination as realUseServerPagination,
useServerInfinite as realUseServerInfinite,
} from './src/index';
export { ConfigurableLink, useStore, useStoreWithActions, createUseStore } from './src/index';
import * as utils from '@openmrs/esm-utils';

Expand Down Expand Up @@ -61,6 +66,10 @@ export const useFeatureFlag = jest.fn().mockReturnValue(true);

export const usePagination = jest.fn(realUsePagination);

export const useServerPagination = jest.fn(realUseServerPagination);

export const useServerInfinite = jest.fn(realUseServerInfinite);

export const useVisit = jest.fn().mockReturnValue({
error: null,
mutate: jest.fn(),
Expand Down Expand Up @@ -106,3 +115,17 @@ export const toOmrsIsoString = jest.fn().mockImplementation((date: Date) => date
export const toDateObjectStrict = jest.fn().mockImplementation((date: string) => new Date(date));

export const getLocale = jest.fn().mockReturnValue('en');

export const useAppContext = jest.fn();

export const useAssignedExtensionIds = jest.fn();

export const useConnectivity = jest.fn();

export const useDefineAppContext = jest.fn();

export const useExtensionSlot = jest.fn();

export const useForceUpdate = jest.fn();

export const usePrimaryIdentifierResource = jest.fn();
2 changes: 2 additions & 0 deletions packages/framework/esm-react-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ export * from './useVisit';
export * from './useVisitTypes';
export * from './usePagination';
export * from './usePrimaryIdentifierResource';
export * from './useServerPagination';
export * from './useServerInfinite';
2 changes: 2 additions & 0 deletions packages/framework/esm-react-utils/src/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ export * from './useVisit';
export * from './useVisitTypes';
export * from './usePagination';
export * from './usePrimaryIdentifierResource';
export * from './useServerPagination';
export * from './useServerInfinite';
9 changes: 9 additions & 0 deletions packages/framework/esm-react-utils/src/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { useCallback, useMemo, useState } from 'react';

const defaultResultsPerPage = 10;

/**
* Use this hook to paginate data that already exists on the client side.
* Note that if the data is obtained from server-side, the caller must handle server-side pagination manually.
* @see `useServerPagination` for hook that automatically manages server-side pagination.
* @see `useServerInfinite` for hook to get all data loaded onto the client-side
* @param data
* @param resultsPerPage
* @returns
*/
export function usePagination<T>(data: Array<T> = [], resultsPerPage = defaultResultsPerPage) {
const [page, setPage] = useState(1);
const totalPages = useMemo(
Expand Down
Loading

0 comments on commit 45bdc9b

Please sign in to comment.