Skip to content

Commit ed25204

Browse files
add react router pagination
1 parent 3759ef4 commit ed25204

6 files changed

Lines changed: 269 additions & 1 deletion

File tree

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@testing-library/react-hooks": "^5.1.3",
4747
"@types/jest": "^26.0.22",
4848
"@types/react": "^17.0.3",
49+
"@types/react-router-dom": "^5.1.8",
4950
"@typescript-eslint/eslint-plugin": "^4.21.0",
5051
"@typescript-eslint/parser": "^4.21.0",
5152
"eslint": "^7.23.0",
@@ -54,9 +55,11 @@
5455
"eslint-plugin-jest": "^24.3.4",
5556
"eslint-plugin-prettier": "^3.3.1",
5657
"jest": "^26.6.3",
58+
"next": "^11.0.1",
5759
"prettier": "^2.2.1",
5860
"react": "^17.0.2",
5961
"react-dom": "^17.0.2",
62+
"react-router-dom": "^5.2.0",
6063
"ts-jest": "^26.5.4",
6164
"typescript": "^4.2.4"
6265
},
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React from 'react'
2+
import { act, renderHook } from '@testing-library/react-hooks'
3+
import { BrowserRouter as Router } from 'react-router-dom'
4+
import { useSearchAndPaginationQueryUrlParameters } from '../reactRouterPagination'
5+
6+
const wrapper: React.FC = ({ children }) => <Router>{children}</Router>
7+
8+
test('should initialize pagination', () => {
9+
const { result } = renderHook(
10+
() => useSearchAndPaginationQueryUrlParameters(),
11+
{ wrapper }
12+
)
13+
14+
expect(result.current.page).toBe(0)
15+
expect(result.current.search).toBe('')
16+
expect(result.current.size).toBe(15)
17+
})
18+
19+
test('should change page', () => {
20+
const { result } = renderHook(
21+
() => useSearchAndPaginationQueryUrlParameters(),
22+
{ wrapper }
23+
)
24+
25+
act(() => {
26+
result.current.paginationActions.setPage(2)
27+
})
28+
29+
expect(result.current.page).toBe(2)
30+
expect(window.location.search).toBe('?page=2')
31+
})
32+
33+
test('should change search', () => {
34+
const { result } = renderHook(
35+
() => useSearchAndPaginationQueryUrlParameters(),
36+
{ wrapper }
37+
)
38+
39+
act(() => {
40+
result.current.searchActions.search('Max')
41+
})
42+
43+
expect(result.current.search).toBe('Max')
44+
expect(window.location.search).toBe('?search=Max')
45+
})
46+
47+
test('on search change -> page should be reset', () => {
48+
const { result } = renderHook(
49+
() => useSearchAndPaginationQueryUrlParameters(),
50+
{ wrapper }
51+
)
52+
53+
act(() => {
54+
result.current.paginationActions.setPage(2)
55+
})
56+
57+
expect(result.current.page).toBe(2)
58+
59+
act(() => {
60+
result.current.searchActions.search('Max')
61+
})
62+
63+
expect(result.current.search).toBe('Max')
64+
expect(result.current.page).toBe(0)
65+
expect(window.location.search).toBe('?search=Max')
66+
})
67+
68+
test('clear pagination should reset search and page', () => {
69+
const { result } = renderHook(
70+
() => useSearchAndPaginationQueryUrlParameters(),
71+
{ wrapper }
72+
)
73+
74+
act(() => {
75+
result.current.searchActions.search('Max')
76+
})
77+
78+
act(() => {
79+
result.current.paginationActions.setPage(2)
80+
})
81+
82+
expect(result.current.search).toBe('Max')
83+
expect(result.current.page).toBe(2)
84+
85+
act(() => {
86+
result.current.searchActions.clear()
87+
})
88+
89+
expect(result.current.search).toBe('')
90+
expect(result.current.page).toBe(0)
91+
expect(window.location.search).toBe('')
92+
})

src/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/nextRouterPagination.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useRouter } from 'next/router'
2+
import { useCallback } from 'react'
3+
4+
import { UseSearchQuery, UsePagination, UseSearchAndPagination } from './types'
5+
import { convert } from './utils'
6+
7+
function getSingleParameterValue(
8+
parameter: string | string[] | undefined
9+
): string | undefined {
10+
return Array.isArray(parameter) ? parameter[0] : parameter
11+
}
12+
13+
export function useSearchQueryUrlParameters(): UseSearchQuery {
14+
const router = useRouter()
15+
16+
return {
17+
search: getSingleParameterValue(router.query.search) || '',
18+
searchActions: {
19+
search: (query: string) => {
20+
const params: { page?: string; size?: string; search?: string } = {
21+
...router.query,
22+
search: query === '' ? undefined : query,
23+
}
24+
25+
delete params['page']
26+
delete params['size']
27+
28+
router.push({
29+
query: params,
30+
})
31+
},
32+
clear: () => {
33+
const params = {
34+
...router.query,
35+
}
36+
37+
delete params['search']
38+
delete params['page']
39+
delete params['size']
40+
41+
router.push({
42+
query: params,
43+
})
44+
},
45+
},
46+
}
47+
}
48+
49+
export function usePaginationQueryUrlParameters(): UsePagination {
50+
const router = useRouter()
51+
52+
const setPage = useCallback(
53+
(page: number) => {
54+
const params = {
55+
...router.query,
56+
page: page.toString(),
57+
}
58+
59+
router.push({
60+
query: params,
61+
})
62+
},
63+
[router]
64+
)
65+
66+
return {
67+
page: convert(getSingleParameterValue(router.query.page) || null, 0),
68+
size: convert(getSingleParameterValue(router.query.size) || null, 15),
69+
paginationActions: {
70+
setPage,
71+
},
72+
}
73+
}
74+
75+
export function useSearchAndPaginationQueryUrlParameters(): UseSearchAndPagination {
76+
const searchParameters = useSearchQueryUrlParameters()
77+
const paginationParameters = usePaginationQueryUrlParameters()
78+
79+
return {
80+
...searchParameters,
81+
...paginationParameters,
82+
}
83+
}

src/reactRouterPagination.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useCallback, useMemo } from 'react'
2+
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'
3+
4+
import { UseSearchQuery, UsePagination, UseSearchAndPagination } from './types'
5+
import { convert } from './utils'
6+
7+
export function useSearchQueryUrlParameters(): UseSearchQuery {
8+
const routerHistory = useHistory()
9+
const { url: routerUrl } = useRouteMatch()
10+
const { search: routeQuery } = useLocation()
11+
12+
const params = useMemo(() => new URLSearchParams(routeQuery), [routeQuery])
13+
14+
return {
15+
search: params.get('search') || '',
16+
searchActions: {
17+
search: (query: string) => {
18+
if (query === '') {
19+
params.delete('search')
20+
} else {
21+
params.set('search', query)
22+
}
23+
params.delete('page')
24+
params.delete('size')
25+
26+
routerHistory.push({
27+
pathname: routerUrl,
28+
search: params.toString(),
29+
})
30+
},
31+
clear: () => {
32+
params.delete('search')
33+
params.delete('page')
34+
params.delete('size')
35+
36+
routerHistory.push({
37+
pathname: routerUrl,
38+
search: params.toString(),
39+
})
40+
},
41+
},
42+
}
43+
}
44+
45+
export function usePaginationQueryUrlParameters(): UsePagination {
46+
const routerHistory = useHistory()
47+
const { search: routeQuery } = useLocation()
48+
const { url: routerUrl } = useRouteMatch()
49+
50+
const params = useMemo(() => new URLSearchParams(routeQuery), [routeQuery])
51+
52+
const setPage = useCallback(
53+
(page: number) => {
54+
params.set('page', page.toString())
55+
routerHistory.push({
56+
pathname: routerUrl,
57+
search: params.toString(),
58+
})
59+
},
60+
[params, routerHistory, routerUrl]
61+
)
62+
63+
return {
64+
page: convert(params.get('page'), 0),
65+
size: convert(params.get('size'), 15),
66+
paginationActions: {
67+
setPage,
68+
},
69+
}
70+
}
71+
72+
export function useSearchAndPaginationQueryUrlParameters(): UseSearchAndPagination {
73+
const searchParameters = useSearchQueryUrlParameters()
74+
const paginationParameters = usePaginationQueryUrlParameters()
75+
76+
return {
77+
...searchParameters,
78+
...paginationParameters,
79+
}
80+
}

src/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function convert(parameter: string | null, fallback: number): number {
2+
if (parameter !== null) {
3+
const converted = parseInt(parameter)
4+
5+
if (Number.isInteger(converted) && converted > 0) {
6+
return converted
7+
}
8+
}
9+
10+
return fallback
11+
}

0 commit comments

Comments
 (0)