Skip to content

Commit c5f35f5

Browse files
Merge pull request #63 from commitd/sh/pagination
feat(usepagination): adds query callback to simplify usePagination use
2 parents e940b12 + 76fc8fd commit c5f35f5

File tree

3 files changed

+113
-39
lines changed

3 files changed

+113
-39
lines changed

src/usePagination/usePagination.stories.tsx

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ import {
1010
Slider,
1111
} from '@committed/components'
1212
import { Meta, Story } from '@storybook/react'
13-
import React, { useEffect, useMemo } from 'react'
13+
import React, { useEffect, useMemo, useState } from 'react'
1414
import useSwr from 'swr'
15-
import { usePagination } from '.'
15+
import { PaginationData, usePagination } from '.'
1616

17-
export interface UsePaginationDocsProps {
17+
export interface UsePaginationDocsProps<T = void> {
1818
/** The total number of items, if know when initializing, use setTotalItems if not */
19-
totalItems: number
19+
totalItems?: number
2020
/** The page to start on */
21-
startPage: number
21+
startPage?: number
2222
/** The size of each page */
23-
pageSize: number
23+
pageSize?: number
24+
/** A function called with the pagination data on data change to prepare the returned `query` object, this is memoed for you */
25+
queryCallback?: (data: PaginationData) => T
2426
}
2527

2628
/**
@@ -120,17 +122,26 @@ type User = {
120122
}
121123

122124
export const ServerSide: Story = () => {
123-
const { page, pageSize, totalPages, setPage, setTotalItems, setPageSize } =
124-
usePagination()
125+
const {
126+
page,
127+
pageSize,
128+
totalPages,
129+
query,
130+
setPage,
131+
setTotalItems,
132+
setPageSize,
133+
} = usePagination({
134+
queryCallback: ({ page, pageSize }) => `page=${page}&per_page=${pageSize}`,
135+
})
125136

126137
const { data } = useSwr(
127-
['https://gorest.co.in/public/v2/users', page, pageSize],
128-
async ([url, page, pageSize]) => {
129-
const res = await fetch(`${url}?page=${page - 1}&per_page=${pageSize}`)
138+
['https://gorest.co.in/public/v2/users', query],
139+
async ([url, query]) => {
140+
const res = await fetch(`${url}?${query}`)
130141

131142
const users = (await res.json()) as User[]
132143
const totalItems = Number(res.headers.get('x-pagination-total'))
133-
144+
setTotalItems(totalItems)
134145
return {
135146
users,
136147
totalItems,
@@ -139,16 +150,6 @@ export const ServerSide: Story = () => {
139150
{ refreshInterval: 0, shouldRetryOnError: false }
140151
)
141152

142-
const users = useMemo(() => {
143-
return data?.users ?? []
144-
}, [data])
145-
146-
useEffect(() => {
147-
if (data?.totalItems) {
148-
setTotalItems(data.totalItems)
149-
}
150-
}, [data])
151-
152153
return (
153154
<Column css={{ gap: '$3' }}>
154155
<Pagination page={page} onPageChange={setPage} count={totalPages} />
@@ -158,7 +159,7 @@ export const ServerSide: Story = () => {
158159
onValueChange={(value) => setPageSize(value[0])}
159160
/>
160161
<div>
161-
{users.map((item) => (
162+
{(data?.users ?? []).map((item) => (
162163
<div>{item.name}</div>
163164
))}
164165
</div>

src/usePagination/usePagination.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { act, renderHook } from '@testing-library/react-hooks'
2-
import { usePagination } from './usePagination'
2+
import { PaginationData, usePagination } from './usePagination'
33

44
test('should start in given state', () => {
55
const { result } = renderHook(() => usePagination())
@@ -132,3 +132,35 @@ test('can set total items', () => {
132132
expect(result.current.page).toBe(10)
133133
expect(result.current.totalPages).toBe(20)
134134
})
135+
136+
test('can update query', () => {
137+
const queryCallback = ({ pageSize, startIndex }: PaginationData) => ({
138+
take: pageSize,
139+
skip: startIndex,
140+
})
141+
const { result, rerender } = renderHook(() =>
142+
usePagination({
143+
totalItems: 100,
144+
pageSize: 10,
145+
queryCallback,
146+
})
147+
)
148+
149+
expect(result.current.query).toStrictEqual({ take: 10, skip: 0 })
150+
151+
act(() => {
152+
result.current.setPage(3)
153+
})
154+
155+
const previous = result.current.query
156+
expect(previous).toStrictEqual({ take: 10, skip: 20 })
157+
158+
// check rerender does not change the query
159+
rerender({
160+
totalItems: 100,
161+
pageSize: 10,
162+
queryCallback,
163+
})
164+
165+
expect(result.current.query).toBe(previous)
166+
})

src/usePagination/usePagination.ts

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import { useCallback, useEffect, useMemo, useState } from 'react'
22

3-
/**
4-
* Utility hook for handling pagination state
5-
*
6-
* returns the current page and functions to manipulate.
7-
*
8-
*/
9-
export function usePagination({
10-
totalItems: startTotalItems = 0,
11-
page: startPage = 1,
12-
pageSize: startPageSize = 20,
13-
}: Partial<{
14-
totalItems: number
15-
page: number
16-
pageSize: number
17-
}> = {}): {
3+
export interface PaginationData {
184
/** The current page */
195
page: number
206
/** The total number of pages */
@@ -29,6 +15,26 @@ export function usePagination({
2915
isPreviousDisabled: boolean
3016
/** The page size */
3117
pageSize: number
18+
}
19+
20+
/**
21+
* Utility hook for handling pagination state
22+
*
23+
* returns the current page and functions to manipulate.
24+
*
25+
*/
26+
export function usePagination<T = void>({
27+
totalItems: startTotalItems = 0,
28+
pageSize: startPageSize = 20,
29+
page: startPage = 1,
30+
queryCallback = () => undefined as T,
31+
}: Partial<{
32+
totalItems: number
33+
pageSize: number
34+
page: number
35+
queryCallback: (data: PaginationData) => T
36+
}> = {}): PaginationData & {
37+
query: T
3238
/** Set the page */
3339
setPage: (page: number) => void
3440
/** Move to the next page */
@@ -78,10 +84,43 @@ export function usePagination({
7884
setPage(page - 1)
7985
}, [page, setPage])
8086

87+
useEffect(() => {
88+
return () => {
89+
setTotalItems(startTotalItems)
90+
}
91+
}, [startTotalItems, setTotalItems])
92+
93+
useEffect(() => {
94+
return () => {
95+
setPageSize(startPageSize)
96+
}
97+
}, [startPageSize, setPageSize])
98+
8199
useEffect(() => {
82100
setPageInternal((page) => Math.max(1, Math.min(page, totalPages)))
83101
}, [totalPages])
84102

103+
const query = useMemo(() => {
104+
return queryCallback({
105+
page,
106+
pageSize,
107+
totalPages,
108+
startIndex,
109+
endIndex,
110+
isNextDisabled,
111+
isPreviousDisabled,
112+
})
113+
}, [
114+
queryCallback,
115+
page,
116+
pageSize,
117+
totalPages,
118+
startIndex,
119+
endIndex,
120+
isNextDisabled,
121+
isPreviousDisabled,
122+
])
123+
85124
return {
86125
page,
87126
pageSize,
@@ -90,13 +129,15 @@ export function usePagination({
90129
endIndex,
91130
isNextDisabled,
92131
isPreviousDisabled,
132+
query,
93133
setPage,
94134
setNextPage,
95135
setPreviousPage,
96136
setPageSize,
97137
setTotalItems,
98138
}
99139
}
140+
100141
function getDerivedData(totalItems: number, pageSize: number, page: number) {
101142
const totalPages = Math.ceil(totalItems / pageSize)
102143
const startIndex = pageSize * (page - 1)

0 commit comments

Comments
 (0)