Skip to content

Commit a8163a3

Browse files
Merge pull request #67 from commitd/sh/usepage
fix(usepagination): fixing prop update issue with pagination
2 parents 9ca98bf + 6bdd4ec commit a8163a3

File tree

3 files changed

+162
-56
lines changed

3 files changed

+162
-56
lines changed

src/usePagination/usePagination.stories.tsx

+56-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Slider,
1111
} from '@committed/components'
1212
import { Meta, Story } from '@storybook/react'
13-
import React, { useEffect, useMemo, useState } from 'react'
13+
import React, { useMemo, useState } from 'react'
1414
import useSwr from 'swr'
1515
import { PaginationData, usePagination } from '.'
1616

@@ -84,7 +84,7 @@ const Template: Story<UsePaginationDocsProps> = (args) => {
8484
}
8585

8686
export const Default = Template.bind({})
87-
Default.args = { totalItems: 100, startPage: 1, pageSize: 20 }
87+
Default.args = { totalItems: 100, pageSize: 20 }
8888

8989
export const ClientSide: Story = () => {
9090
const items = Array.from({ length: 100 }, (v, i) => i)
@@ -97,7 +97,7 @@ export const ClientSide: Story = () => {
9797
<>
9898
<Row css={{ gap: '$2', mb: '$3' }}>
9999
{items.slice(startIndex, endIndex).map((item) => (
100-
<div>{item}</div>
100+
<div key={`${item}`}>{item}</div>
101101
))}
102102
</Row>
103103
<Pagination page={page} onPageChange={setPage} count={totalPages} />
@@ -160,7 +160,7 @@ export const ServerSide: Story = () => {
160160
/>
161161
<div>
162162
{(data?.users ?? []).map((item) => (
163-
<div>{item.name}</div>
163+
<div key={`${item.name}`}>{item.name}</div>
164164
))}
165165
</div>
166166
</Column>
@@ -217,3 +217,55 @@ MakeYourOwn.parameters = {
217217
},
218218
},
219219
}
220+
221+
const ChildWithPagination = ({ items, pageSize }) => {
222+
const { page, totalPages, startIndex, endIndex, setPage } = usePagination({
223+
totalItems: items.length,
224+
pageSize,
225+
})
226+
227+
return (
228+
<>
229+
<Row css={{ gap: '$2', mb: '$3' }}>
230+
{items.slice(startIndex, endIndex).map((item) => (
231+
<div key={`${item}`}>{item}</div>
232+
))}
233+
</Row>
234+
<Pagination page={page} onPageChange={setPage} count={totalPages} />
235+
</>
236+
)
237+
}
238+
239+
export const PropChangeTest: Story = () => {
240+
const [totalItems, setTotalItems] = useState(100)
241+
const [pageSize, setPageSize] = useState(10)
242+
243+
const items = useMemo(
244+
() => Array.from({ length: totalItems }, (v, i) => i),
245+
[totalItems]
246+
)
247+
248+
return (
249+
<>
250+
<Slider
251+
labelFunction={(value) => `Items ${value}`}
252+
value={[totalItems]}
253+
onValueChange={(value) => setTotalItems(value[0])}
254+
/>
255+
<Slider
256+
labelFunction={(value) => `Page size ${value}`}
257+
value={[pageSize]}
258+
onValueChange={(value) => setPageSize(value[0])}
259+
/>
260+
<ChildWithPagination items={items} pageSize={pageSize} />
261+
</>
262+
)
263+
}
264+
PropChangeTest.parameters = {
265+
docs: {
266+
description: {
267+
story:
268+
'A test story to check the values correctly update when the component props change.',
269+
},
270+
},
271+
}

src/usePagination/usePagination.test.ts

+41-12
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,26 @@ test('should start in given state', () => {
2323

2424
test('should start using the provided start state', () => {
2525
const { result } = renderHook(() =>
26-
usePagination({ page: 5, totalItems: 100, pageSize: 10 })
26+
usePagination({ totalItems: 100, pageSize: 10 })
2727
)
2828
const {
2929
page,
3030
totalPages,
31+
totalItems,
3132
startIndex,
3233
endIndex,
3334
pageSize,
3435
isPreviousDisabled,
3536
isNextDisabled,
3637
} = result.current
37-
expect(page).toBe(5)
38-
expect(totalPages).toBe(10)
39-
expect(startIndex).toBe(40)
40-
expect(endIndex).toBe(50)
38+
expect(page).toBe(1)
4139
expect(pageSize).toBe(10)
40+
expect(totalItems).toBe(100)
41+
expect(totalPages).toBe(10)
42+
expect(startIndex).toBe(0)
43+
expect(endIndex).toBe(10)
4244
expect(isNextDisabled).toBe(false)
43-
expect(isPreviousDisabled).toBe(false)
45+
expect(isPreviousDisabled).toBe(true)
4446
})
4547

4648
test('can set page', () => {
@@ -75,8 +77,14 @@ test('can set page', () => {
7577

7678
test('can not set page too high', () => {
7779
const { result } = renderHook(() =>
78-
usePagination({ page: 10, totalItems: 100, pageSize: 10 })
80+
usePagination({ totalItems: 100, pageSize: 10 })
7981
)
82+
expect(result.current.page).toBe(1)
83+
84+
act(() => {
85+
result.current.setPage(10)
86+
})
87+
8088
expect(result.current.page).toBe(10)
8189

8290
act(() => {
@@ -92,7 +100,7 @@ test('can not set page too high', () => {
92100

93101
test('can not set page too low', () => {
94102
const { result } = renderHook(() =>
95-
usePagination({ page: 1, totalItems: 100, pageSize: 10 })
103+
usePagination({ totalItems: 100, pageSize: 10 })
96104
)
97105
expect(result.current.page).toBe(1)
98106

@@ -109,27 +117,27 @@ test('can not set page too low', () => {
109117

110118
test('can set page size', () => {
111119
const { result } = renderHook(() =>
112-
usePagination({ page: 10, totalItems: 100, pageSize: 10 })
120+
usePagination({ totalItems: 100, pageSize: 10 })
113121
)
114122

115123
act(() => {
116124
result.current.setPageSize(20)
117125
})
118126

119-
expect(result.current.page).toBe(5)
127+
expect(result.current.page).toBe(1)
120128
expect(result.current.pageSize).toBe(20)
121129
})
122130

123131
test('can set total items', () => {
124132
const { result } = renderHook(() =>
125-
usePagination({ page: 10, totalItems: 100, pageSize: 10 })
133+
usePagination({ totalItems: 100, pageSize: 10 })
126134
)
127135

128136
act(() => {
129137
result.current.setTotalItems(200)
130138
})
131139

132-
expect(result.current.page).toBe(10)
140+
expect(result.current.page).toBe(1)
133141
expect(result.current.totalPages).toBe(20)
134142
})
135143

@@ -164,3 +172,24 @@ test('can update query', () => {
164172

165173
expect(result.current.query).toBe(previous)
166174
})
175+
176+
test('can update total items', async () => {
177+
const { result, rerender } = renderHook(usePagination, {
178+
initialProps: { totalItems: 100, pageSize: 10 },
179+
})
180+
act(() => {
181+
result.current.setPage(2)
182+
})
183+
184+
expect(result.current.page).toBe(2)
185+
expect(result.current.totalPages).toBe(10)
186+
expect(result.current.startIndex).toBe(10)
187+
expect(result.current.endIndex).toBe(20)
188+
189+
act(() => rerender({ totalItems: 200, pageSize: 15 }))
190+
191+
expect(result.current.page).toBe(2)
192+
expect(result.current.totalPages).toBe(14)
193+
expect(result.current.startIndex).toBe(15)
194+
expect(result.current.endIndex).toBe(30)
195+
})

src/usePagination/usePagination.ts

+65-40
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ export interface PaginationData {
1515
isPreviousDisabled: boolean
1616
/** The page size */
1717
pageSize: number
18+
/** The total items */
19+
totalItems: number
20+
}
21+
22+
const defaultOptions = {
23+
totalItems: 0,
24+
pageSize: 20,
1825
}
1926

2027
/**
@@ -24,14 +31,12 @@ export interface PaginationData {
2431
*
2532
*/
2633
export function usePagination<T = void>({
27-
totalItems: startTotalItems = 0,
28-
pageSize: startPageSize = 20,
29-
page: startPage = 1,
30-
queryCallback = () => undefined as T,
34+
queryCallback,
35+
totalItems: propTotalItems = defaultOptions.totalItems,
36+
pageSize: propPageSize = defaultOptions.pageSize,
3137
}: Partial<{
3238
totalItems: number
3339
pageSize: number
34-
page: number
3540
queryCallback: (data: PaginationData) => T
3641
}> = {}): PaginationData & {
3742
query: T
@@ -46,9 +51,55 @@ export function usePagination<T = void>({
4651
/** set the number of items */
4752
setTotalItems: (totalItemsSize: number) => void
4853
} {
49-
const [totalItems, setTotalItemsInternal] = useState(startTotalItems)
50-
const [pageSize, setPageSizeInternal] = useState(Math.max(startPageSize, 1))
51-
const [page, setPageInternal] = useState(Math.max(startPage, 1))
54+
const [page, setPageInternal] = useState(1)
55+
56+
const [data, setDataInternal] = useState({
57+
totalItems: Math.max(propTotalItems, 0),
58+
pageSize: Math.max(propPageSize, 1),
59+
})
60+
61+
const setData = useCallback(
62+
(
63+
newData: Partial<{
64+
totalItems: number
65+
pageSize: number
66+
}>
67+
) => {
68+
setDataInternal((current) => ({
69+
pageSize: Math.max(1, newData.pageSize ?? current.pageSize),
70+
totalItems: Math.max(0, newData.totalItems ?? current.totalItems),
71+
}))
72+
},
73+
[setDataInternal]
74+
)
75+
const setTotalItems = useCallback(
76+
(totalItems: number) => {
77+
setData({ totalItems })
78+
},
79+
[setData]
80+
)
81+
82+
const setPageSize = useCallback(
83+
(pageSize: number) => {
84+
setData({ pageSize })
85+
},
86+
[setData]
87+
)
88+
89+
const savedCallback = useRef<(data: PaginationData) => T>(
90+
queryCallback ?? (() => undefined as T)
91+
)
92+
93+
useEffect(() => {
94+
setData({
95+
totalItems: propTotalItems,
96+
pageSize: propPageSize,
97+
})
98+
}, [propTotalItems, propPageSize, setData])
99+
100+
useEffect(() => {
101+
savedCallback.current = queryCallback ?? (() => undefined as T)
102+
}, [queryCallback])
52103

53104
const {
54105
totalPages,
@@ -57,18 +108,10 @@ export function usePagination<T = void>({
57108
isNextDisabled,
58109
isPreviousDisabled,
59110
} = useMemo(
60-
() => getDerivedData(totalItems, pageSize, page),
61-
[page, pageSize, totalItems]
111+
() => getDerivedData(data.totalItems, data.pageSize, page),
112+
[page, data]
62113
)
63114

64-
const setTotalItems = useCallback((newTotalItems: number) => {
65-
setTotalItemsInternal(Math.max(0, newTotalItems))
66-
}, [])
67-
68-
const setPageSize = useCallback((newPageSize: number) => {
69-
setPageSizeInternal(Math.max(1, newPageSize))
70-
}, [])
71-
72115
const setPage = useCallback(
73116
(newPage: number) => {
74117
setPageInternal(Math.max(1, Math.min(newPage, totalPages)))
@@ -84,34 +127,15 @@ export function usePagination<T = void>({
84127
setPage(page - 1)
85128
}, [page, setPage])
86129

87-
useEffect(() => {
88-
return () => {
89-
setTotalItems(startTotalItems)
90-
}
91-
}, [startTotalItems, setTotalItems])
92-
93-
useEffect(() => {
94-
return () => {
95-
setPageSize(startPageSize)
96-
}
97-
}, [startPageSize, setPageSize])
98-
99130
useEffect(() => {
100131
setPageInternal((page) => Math.max(1, Math.min(page, totalPages)))
101132
}, [totalPages])
102133

103-
const savedCallback = useRef<(data: PaginationData) => T>(queryCallback)
104-
105-
// Remember the latest callback.
106-
useEffect(() => {
107-
savedCallback.current = queryCallback
108-
}, [queryCallback])
109-
110134
const query = useMemo(() => {
111135
const callback = savedCallback.current
112136
return callback({
113137
page,
114-
pageSize,
138+
...data,
115139
totalPages,
116140
startIndex,
117141
endIndex,
@@ -120,7 +144,7 @@ export function usePagination<T = void>({
120144
})
121145
}, [
122146
page,
123-
pageSize,
147+
data,
124148
totalPages,
125149
startIndex,
126150
endIndex,
@@ -130,7 +154,8 @@ export function usePagination<T = void>({
130154

131155
return {
132156
page,
133-
pageSize,
157+
pageSize: data.pageSize,
158+
totalItems: data.totalItems,
134159
totalPages,
135160
startIndex,
136161
endIndex,

0 commit comments

Comments
 (0)