Skip to content

Commit 00d6e74

Browse files
authored
refactor: handling zero and one based numbering in Pagination (freeCodeCamp-2025-Summer-Hackathon#255)
1 parent 8ce8080 commit 00d6e74

File tree

5 files changed

+97
-50
lines changed

5 files changed

+97
-50
lines changed

frontend/src/components/IdeasList.jsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useMemo, useState } from 'react';
22
import { IdeaListItem } from './IdeaListItem';
33
import { useApi } from '../hooks/useApi';
4-
import { Pagination } from './Pagination';
4+
import { Page, Pagination } from './Pagination';
55
import Spinny from './Spinny';
66
import { Link } from 'react-router';
77

@@ -11,7 +11,7 @@ const IdeasList = ({
1111
noIdeasText = "There's no ideas!",
1212
count,
1313
sort = null,
14-
page = 0,
14+
page = Page.fromZeroBased(0),
1515
paginate = false,
1616
showExploreButton = false,
1717
}) => {
@@ -23,19 +23,23 @@ const IdeasList = ({
2323
});
2424

2525
const getApiUrl = useCallback(
26-
(page = 0) => {
26+
(page = Page.fromZeroBased(0)) => {
2727
const sorting = sort ? `&sort=${sort}` : '';
28-
const skip = page > 0 ? `&skip=${page * count}` : '';
28+
const skip = page.number > 0 ? `&skip=${page.number * count}` : '';
2929
return `${base}?limit=${count}${sorting}${skip}`;
3030
},
3131
[count, sort, base]
3232
);
3333

34-
const getPageUrl = useCallback(page => `${base}page/${page + 1}`, [base]);
34+
const getPageUrl = useCallback(
35+
page => `${base}page/${page.displayNumber}`,
36+
[base]
37+
);
3538

39+
const fetchUrl = getApiUrl(page);
3640
useEffect(() => {
37-
fetchFromApi(getApiUrl(page));
38-
}, [fetchFromApi, getApiUrl, page]);
41+
fetchFromApi(fetchUrl);
42+
}, [fetchFromApi, fetchUrl]);
3943

4044
useEffect(() => {
4145
if (data?.data?.length > 0) {

frontend/src/components/Pagination.jsx

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,92 @@
11
import { useEffect, useState } from 'react';
22
import { useParams } from 'react-router';
33

4+
export class Page {
5+
number = 0;
6+
displayNumber = 1;
7+
8+
constructor(page, displayPage) {
9+
this.number = page;
10+
this.displayNumber = displayPage;
11+
}
12+
13+
static fromZeroBased(page) {
14+
return new Page(page, page + 1);
15+
}
16+
17+
static fromOneBased(displayPage) {
18+
return new Page(displayPage - 1, displayPage);
19+
}
20+
}
21+
422
export const Pagination = ({
523
numberOfPages,
624
initialPage,
725
getPageUrl,
826
fetchPage,
927
}) => {
10-
const [currentPage, setCurPage] = useState(initialPage);
11-
const pages = [...Array(numberOfPages).keys()];
12-
const { page: paramPage = 1 } = useParams();
28+
const [activePage, setActivePage] = useState(initialPage);
29+
const pages = [...Array(numberOfPages).keys()].map(Page.fromZeroBased);
30+
const { page: paramPageOneBased = 1 } = useParams();
1331

1432
useEffect(() => {
15-
setCurPage(parseInt(paramPage) - 1);
16-
}, [paramPage]);
33+
setActivePage(Page.fromOneBased(parseInt(paramPageOneBased)));
34+
}, [paramPageOneBased]);
35+
36+
const Href = ({ page = null, active = false, children }) => {
37+
if (page === null || active) {
38+
return (
39+
<span className={`nav-link${active ? ' active' : ''}`}>{children}</span>
40+
);
41+
}
1742

18-
const Href = ({ pageNo, text }) => {
19-
const pageUrl = getPageUrl(pageNo);
43+
const pageUrl = getPageUrl(page);
2044
const onClick = async e => {
2145
e.preventDefault();
22-
await fetchPage(pageNo);
23-
setCurPage(pageNo);
46+
await fetchPage(page);
47+
setActivePage(page);
2448
window.history.pushState(null, '', pageUrl);
2549
};
2650
return (
2751
<a href={pageUrl} className='nav-link' onClick={onClick}>
28-
{text}
52+
{children}
2953
</a>
3054
);
3155
};
3256

33-
const NavigateToPrevPage = ({ currentPage }) =>
34-
currentPage <= 0 ? (
35-
<span className='nav-link'>{'<'}</span>
36-
) : (
37-
<Href pageNo={currentPage - 1} text={'<'} />
38-
);
57+
const NavigateToPrevPage = ({ currentPage }) => (
58+
<Href
59+
{...(currentPage.number > 0 && {
60+
page: Page.fromZeroBased(currentPage.number - 1),
61+
})}
62+
>
63+
{'<'}
64+
</Href>
65+
);
3966

40-
const NavigateToNextPage = ({ currentPage }) =>
41-
currentPage >= numberOfPages - 1 ? (
42-
<span className='nav-link'>{'>'}</span>
43-
) : (
44-
<Href pageNo={currentPage + 1} text={'>'} />
45-
);
67+
const NavigateToNextPage = ({ currentPage }) => (
68+
<Href
69+
{...(currentPage.number < numberOfPages && {
70+
page: Page.fromZeroBased(currentPage.number + 1),
71+
})}
72+
>
73+
{'>'}
74+
</Href>
75+
);
4676

4777
return (
4878
<div className='nav-list'>
49-
<NavigateToPrevPage currentPage={currentPage} />
50-
{pages.map(pageNo =>
51-
currentPage !== pageNo ? (
52-
<Href key={pageNo} pageNo={pageNo} text={pageNo + 1} />
53-
) : (
54-
<span className='nav-link active' key={pageNo}>
55-
{pageNo + 1}
56-
</span>
57-
)
58-
)}
59-
<NavigateToNextPage currentPage={currentPage} />
79+
<NavigateToPrevPage currentPage={activePage} />
80+
{pages.map(page => (
81+
<Href
82+
key={page.number}
83+
page={page}
84+
{...(activePage.number === page.number && { active: true })}
85+
>
86+
{page.displayNumber}
87+
</Href>
88+
))}
89+
<NavigateToNextPage currentPage={activePage} />
6090
</div>
6191
);
6292
};

frontend/src/pages/Ideas/IdeasPage.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { useParams } from 'react-router';
22
import IdeasList from '../../components/IdeasList';
3+
import { Page } from '../../components/Pagination';
34

45
export const IdeasPage = ({ headerText = 'All Ideas' }) => {
5-
const { page = 1 } = useParams('page');
6+
const { page: pageOneBased = 1 } = useParams('page');
67
return (
7-
<IdeasList {...{ count: 20, page: page - 1, paginate: true, headerText }} />
8+
<IdeasList
9+
{...{
10+
count: 20,
11+
page: Page.fromOneBased(parseInt(pageOneBased)),
12+
paginate: true,
13+
headerText,
14+
}}
15+
/>
816
);
917
};
1018

frontend/src/pages/MyIdeasPage.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useParams } from 'react-router';
22
import IdeasList from '../components/IdeasList';
3+
import { Page } from '../components/Pagination';
34

45
export const MyIdeasPage = ({ headerText = 'My Ideas' }) => {
56
const { page = 1 } = useParams('page');
@@ -8,7 +9,7 @@ export const MyIdeasPage = ({ headerText = 'My Ideas' }) => {
89
{...{
910
base: '/me/ideas/',
1011
count: 20,
11-
page: page - 1,
12+
page: Page.fromOneBased(parseInt(page)),
1213
paginate: true,
1314
headerText,
1415
noIdeasText: "You don't have any ideas added.",

frontend/src/pages/UsersPage.jsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,34 @@ import { useParams, useNavigate } from 'react-router';
33
import { useUser } from '../hooks/useUser';
44
import { useApi } from '../hooks/useApi';
55
import Spinny from '../components/Spinny';
6-
import { Pagination } from '../components/Pagination';
6+
import { Page, Pagination } from '../components/Pagination';
77
import { Link } from 'react-router';
88

99
const UsersPage = ({ count = 20 }) => {
1010
const { isLogged, isAdmin } = useUser();
1111
const navigate = useNavigate();
1212
const { page = 1 } = useParams();
13-
const currentPage = parseInt(page, 10) - 1;
13+
const currentPage = Page.fromOneBased(parseInt(page));
1414

1515
const [users, setUsers] = useState([]);
1616
const [totalPages, setTotalPages] = useState(0);
1717

1818
const { data, error, isLoading, fetchFromApi } = useApi();
1919

2020
const getApiUrl = useCallback(
21-
(pageNo = 0) => {
22-
const skip = pageNo > 0 ? `&skip=${pageNo * count}` : '';
21+
(page = Page.fromZeroBased(0)) => {
22+
const skip = page.number > 0 ? `&skip=${page.number * count}` : '';
2323
return `/users/?limit=${count}${skip}`;
2424
},
2525
[count]
2626
);
2727

28-
const getPageUrl = useCallback(pageNo => `/users/page/${pageNo + 1}`, []);
28+
const getPageUrl = useCallback(
29+
page => `/users/page/${page.displayNumber}`,
30+
[]
31+
);
2932

33+
const fetchUrl = getApiUrl(currentPage);
3034
useEffect(() => {
3135
if (!isLogged) {
3236
navigate('/login');
@@ -37,8 +41,8 @@ const UsersPage = ({ count = 20 }) => {
3741
return;
3842
}
3943

40-
fetchFromApi(getApiUrl(currentPage));
41-
}, [isLogged, isAdmin, navigate, currentPage, fetchFromApi, getApiUrl]);
44+
fetchFromApi(fetchUrl);
45+
}, [isLogged, isAdmin, navigate, fetchFromApi, fetchUrl]);
4246

4347
useEffect(() => {
4448
if (data && !error) {

0 commit comments

Comments
 (0)