Skip to content

Commit 634db48

Browse files
committed
refactor(usePagination): manage state internally with optional overrides
1 parent ef23eaf commit 634db48

File tree

2 files changed

+53
-98
lines changed

2 files changed

+53
-98
lines changed

src/hooks/usePagination/usePagination.test.ts

Lines changed: 44 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { describe, test, expect, vi } from 'vitest'
1+
import { describe, test, expect } from 'vitest'
22
import { renderHook, act } from '@testing-library/react'
33
import { usePagination, extractPaginationMeta } from './usePagination'
4-
import type { PaginationItemsPerPage } from '@/components/Common/PaginationControl/PaginationControlTypes'
54

65
const createMockHttpMeta = (headers: Record<string, string> = {}) => ({
76
response: {
@@ -54,169 +53,124 @@ describe('extractPaginationMeta', () => {
5453
})
5554

5655
describe('usePagination', () => {
57-
const createState = (
58-
overrides: Partial<{
59-
currentPage: number
60-
itemsPerPage: PaginationItemsPerPage
61-
setCurrentPage: React.Dispatch<React.SetStateAction<number>>
62-
setItemsPerPage: React.Dispatch<React.SetStateAction<PaginationItemsPerPage>>
63-
}> = {},
64-
) => ({
65-
currentPage: 1,
66-
itemsPerPage: 10 as PaginationItemsPerPage,
67-
setCurrentPage: vi.fn(),
68-
setItemsPerPage: vi.fn(),
69-
...overrides,
70-
})
71-
72-
describe('state management', () => {
73-
test('returns currentPage from state', () => {
74-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
75-
const state = createState({ currentPage: 3 })
56+
describe('initial state', () => {
57+
test('defaults currentPage to 1', () => {
58+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
59+
const { result } = renderHook(() => usePagination(httpMeta))
60+
expect(result.current.currentPage).toBe(1)
61+
})
7662

77-
const { result } = renderHook(() => usePagination(httpMeta, state))
63+
test('defaults itemsPerPage to 5', () => {
64+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
65+
const { result } = renderHook(() => usePagination(httpMeta))
66+
expect(result.current.itemsPerPage).toBe(5)
67+
})
7868

69+
test('uses initialPage from options', () => {
70+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
71+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 3 }))
7972
expect(result.current.currentPage).toBe(3)
8073
})
8174

82-
test('returns itemsPerPage from state', () => {
83-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
84-
const state = createState({ itemsPerPage: 50 })
85-
86-
const { result } = renderHook(() => usePagination(httpMeta, state))
87-
75+
test('uses initialItemsPerPage from options', () => {
76+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
77+
const { result } = renderHook(() => usePagination(httpMeta, { initialItemsPerPage: 50 }))
8878
expect(result.current.itemsPerPage).toBe(50)
8979
})
80+
})
9081

82+
describe('navigation handlers', () => {
9183
test('handleFirstPage sets page to 1', () => {
92-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
93-
const setCurrentPage = vi.fn()
94-
const state = createState({ currentPage: 3, setCurrentPage })
95-
96-
const { result } = renderHook(() => usePagination(httpMeta, state))
84+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
85+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 3 }))
9786

9887
act(() => {
9988
result.current.handleFirstPage()
10089
})
10190

102-
expect(setCurrentPage).toHaveBeenCalledWith(1)
91+
expect(result.current.currentPage).toBe(1)
10392
})
10493

10594
test('handlePreviousPage decrements page', () => {
106-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
107-
const setCurrentPage = vi.fn()
108-
const state = createState({ currentPage: 3, setCurrentPage })
109-
110-
const { result } = renderHook(() => usePagination(httpMeta, state))
95+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
96+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 3 }))
11197

11298
act(() => {
11399
result.current.handlePreviousPage()
114100
})
115101

116-
expect(setCurrentPage).toHaveBeenCalled()
117-
const updateFn = setCurrentPage.mock.calls[0]?.[0] as ((prev: number) => number) | undefined
118-
expect(updateFn).toBeDefined()
119-
expect(updateFn?.(3)).toBe(2)
102+
expect(result.current.currentPage).toBe(2)
120103
})
121104

122105
test('handleNextPage increments page', () => {
123-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
124-
const setCurrentPage = vi.fn()
125-
const state = createState({ currentPage: 3, setCurrentPage })
126-
127-
const { result } = renderHook(() => usePagination(httpMeta, state))
106+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
107+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 3 }))
128108

129109
act(() => {
130110
result.current.handleNextPage()
131111
})
132112

133-
expect(setCurrentPage).toHaveBeenCalled()
134-
const updateFn = setCurrentPage.mock.calls[0]?.[0] as ((prev: number) => number) | undefined
135-
expect(updateFn).toBeDefined()
136-
expect(updateFn?.(3)).toBe(4)
113+
expect(result.current.currentPage).toBe(4)
137114
})
138115

139116
test('handleLastPage sets page to totalPages', () => {
140-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
141-
const setCurrentPage = vi.fn()
142-
const state = createState({ setCurrentPage })
143-
144-
const { result } = renderHook(() => usePagination(httpMeta, state))
117+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
118+
const { result } = renderHook(() => usePagination(httpMeta))
145119

146120
act(() => {
147121
result.current.handleLastPage()
148122
})
149123

150-
expect(setCurrentPage).toHaveBeenCalledWith(5)
124+
expect(result.current.currentPage).toBe(5)
151125
})
152126

153127
test('handleItemsPerPageChange updates itemsPerPage and resets to page 1', () => {
154-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
155-
const setCurrentPage = vi.fn()
156-
const setItemsPerPage = vi.fn()
157-
const state = createState({ currentPage: 3, setCurrentPage, setItemsPerPage })
158-
159-
const { result } = renderHook(() => usePagination(httpMeta, state))
128+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
129+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 3 }))
160130

161131
act(() => {
162132
result.current.handleItemsPerPageChange(50)
163133
})
164134

165-
expect(setItemsPerPage).toHaveBeenCalledWith(50)
166-
expect(setCurrentPage).toHaveBeenCalledWith(1)
135+
expect(result.current.itemsPerPage).toBe(50)
136+
expect(result.current.currentPage).toBe(1)
167137
})
168138
})
169139

170140
describe('edge cases', () => {
171141
test('handlePreviousPage on page 1 stays on page 1', () => {
172-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
173-
const setCurrentPage = vi.fn()
174-
const state = createState({ currentPage: 1, setCurrentPage })
175-
176-
const { result } = renderHook(() => usePagination(httpMeta, state))
142+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
143+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 1 }))
177144

178145
act(() => {
179146
result.current.handlePreviousPage()
180147
})
181148

182-
const updateFn = setCurrentPage.mock.calls[0]?.[0] as ((prev: number) => number) | undefined
183-
expect(updateFn).toBeDefined()
184-
expect(updateFn?.(1)).toBe(1)
149+
expect(result.current.currentPage).toBe(1)
185150
})
186151

187152
test('handleNextPage on last page stays on last page', () => {
188-
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5', 'x-total-count': '50' })
189-
const setCurrentPage = vi.fn()
190-
const state = createState({ currentPage: 5, setCurrentPage })
191-
192-
const { result } = renderHook(() => usePagination(httpMeta, state))
153+
const httpMeta = createMockHttpMeta({ 'x-total-pages': '5' })
154+
const { result } = renderHook(() => usePagination(httpMeta, { initialPage: 5 }))
193155

194156
act(() => {
195157
result.current.handleNextPage()
196158
})
197159

198-
const updateFn = setCurrentPage.mock.calls[0]?.[0] as ((prev: number) => number) | undefined
199-
expect(updateFn).toBeDefined()
200-
expect(updateFn?.(5)).toBe(5)
160+
expect(result.current.currentPage).toBe(5)
201161
})
202162
})
203163

204164
describe('header extraction', () => {
205165
test('returns totalPages from httpMeta', () => {
206166
const httpMeta = createMockHttpMeta({ 'x-total-pages': '10', 'x-total-count': '100' })
207-
const state = createState()
208-
209-
const { result } = renderHook(() => usePagination(httpMeta, state))
210-
167+
const { result } = renderHook(() => usePagination(httpMeta))
211168
expect(result.current.totalPages).toBe(10)
212169
})
213170

214171
test('returns totalItems from httpMeta', () => {
215172
const httpMeta = createMockHttpMeta({ 'x-total-pages': '10', 'x-total-count': '100' })
216-
const state = createState()
217-
218-
const { result } = renderHook(() => usePagination(httpMeta, state))
219-
173+
const { result } = renderHook(() => usePagination(httpMeta))
220174
expect(result.current.totalItems).toBe(100)
221175
})
222176
})

src/hooks/usePagination/usePagination.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { useCallback, useMemo } from 'react'
1+
import { useCallback, useMemo, useState } from 'react'
22
import type { PaginationItemsPerPage } from '@/components/Common/PaginationControl/PaginationControlTypes'
33

44
type HttpMeta = {
55
response: Response
66
}
77

8-
type PaginationState = {
9-
currentPage: number
10-
itemsPerPage: PaginationItemsPerPage
11-
setCurrentPage: React.Dispatch<React.SetStateAction<number>>
12-
setItemsPerPage: React.Dispatch<React.SetStateAction<PaginationItemsPerPage>>
8+
type UsePaginationOptions = {
9+
initialPage?: number
10+
initialItemsPerPage?: PaginationItemsPerPage
1311
}
1412

1513
function parseHeaderInt(value: string | null, defaultValue: number): number {
@@ -25,8 +23,11 @@ export function extractPaginationMeta(httpMeta?: HttpMeta | null) {
2523
}
2624
}
2725

28-
export function usePagination(httpMeta: HttpMeta | undefined, state: PaginationState) {
29-
const { currentPage, itemsPerPage, setCurrentPage, setItemsPerPage } = state
26+
export function usePagination(httpMeta: HttpMeta | undefined, options?: UsePaginationOptions) {
27+
const [currentPage, setCurrentPage] = useState(options?.initialPage ?? 1)
28+
const [itemsPerPage, setItemsPerPage] = useState<PaginationItemsPerPage>(
29+
options?.initialItemsPerPage ?? 5,
30+
)
3031

3132
const { totalPages, totalItems } = extractPaginationMeta(httpMeta)
3233

0 commit comments

Comments
 (0)