From 228517268c5f1097b7c0a0740fe1de4d3e30e99a Mon Sep 17 00:00:00 2001
From: Artem Faust <91615835+IronHedgehog@users.noreply.github.com>
Date: Mon, 29 Jun 2026 17:08:48 +0300
Subject: [PATCH 1/2] feat: add pagination
---
package-lock.json | 12 ++-
package.json | 2 +-
src/App.tsx | 126 +++++++++--------------
src/components/Pagination/Pagination.tsx | 88 +++++++++++++++-
4 files changed, 141 insertions(+), 87 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index bc04a7602..cb85c1267 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
- "@mate-academy/scripts": "^1.9.12",
+ "@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
@@ -1183,10 +1183,11 @@
}
},
"node_modules/@mate-academy/scripts": {
- "version": "1.9.12",
- "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.9.12.tgz",
- "integrity": "sha512-/OcmxMa34lYLFlGx7Ig926W1U1qjrnXbjFJ2TzUcDaLmED+A5se652NcWwGOidXRuMAOYLPU2jNYBEkKyXrFJA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz",
+ "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@octokit/rest": "^17.11.2",
"@types/get-port": "^4.2.0",
@@ -3256,7 +3257,8 @@
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
- "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
},
"node_modules/clean-stack": {
"version": "2.2.0",
diff --git a/package.json b/package.json
index f412fefe6..617386d19 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
- "@mate-academy/scripts": "^1.9.12",
+ "@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
diff --git a/src/App.tsx b/src/App.tsx
index 189a990c8..51c01759e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,31 @@
-import React from 'react';
+import React, { useState } from 'react';
import './App.css';
+import { Pagination } from './components/Pagination';
import { getNumbers } from './utils';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const items = getNumbers(1, 42).map(n => `Item ${n}`);
+enum PerPage {
+ THREE = 3,
+ FIVE = 5,
+ TEN = 10,
+ TWENTY = 20,
+}
+
+const items: string[] = getNumbers(1, 42).map(n => `Item ${n}`);
export const App: React.FC = () => {
+ const [page, setPage] = useState(1);
+ const [perPage, setPerPage] = useState(PerPage.FIVE);
+
+ const startIndex = (page - 1) * perPage;
+ const endIndex = Math.min(startIndex + perPage, items.length);
+ const visibleItems = items.slice(startIndex, endIndex);
+
return (
Items with Pagination
- Page 1 (items 1 - 5 of 42)
+ Page {page} (items {startIndex + 1} - {endIndex} of {items.length})
@@ -19,11 +33,17 @@ export const App: React.FC = () => {
@@ -32,78 +52,24 @@ export const App: React.FC = () => {
- {/* Move this markup to Pagination */}
-
+ {
+ if (page !== selectedPage) {
+ setPage(selectedPage);
+ }
+ }}
+ />
- - Item 1
- - Item 2
- - Item 3
- - Item 4
- - Item 5
+ {visibleItems.map(item => {
+ return (
+ -
+ {item}
+
+ );
+ })}
);
diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx
index e417a09fc..0fb15487a 100644
--- a/src/components/Pagination/Pagination.tsx
+++ b/src/components/Pagination/Pagination.tsx
@@ -1 +1,87 @@
-export const Pagination = () => {};
+import cn from 'classnames';
+import React from 'react';
+interface Props {
+ total: number;
+ perPage: number;
+ currentPage: number;
+ onPageChange: (selectedPage: number) => void;
+}
+
+export const Pagination: React.FC = ({
+ total,
+ perPage = 5,
+ currentPage = 1,
+ onPageChange,
+}) => {
+ const totalPages: number = Math.ceil(total / perPage);
+ const pages: number[] = Array.from({ length: totalPages }, (_, i) => i + 1);
+
+ return (
+
+ );
+};
From 2baf70589f715a9bf217cb7466f959290ed89193 Mon Sep 17 00:00:00 2001
From: Artem Faust <91615835+IronHedgehog@users.noreply.github.com>
Date: Mon, 29 Jun 2026 17:23:49 +0300
Subject: [PATCH 2/2] fix: add react-router
---
package-lock.json | 42 ++++++++++++++++++++++++
package.json | 1 +
src/App.tsx | 31 ++++++++++-------
src/components/Pagination/Pagination.tsx | 6 ++--
src/index.tsx | 8 ++++-
5 files changed, 73 insertions(+), 15 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index cb85c1267..f3faea4e6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-router": "^7.18.0",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
@@ -3405,6 +3406,19 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -8736,6 +8750,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
+ "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -9234,6 +9270,12 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
diff --git a/package.json b/package.json
index 617386d19..f67a81e92 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-router": "^7.18.0",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
diff --git a/src/App.tsx b/src/App.tsx
index 51c01759e..e8bfabb83 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,6 @@
-import React, { useState } from 'react';
+import React from 'react';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { useSearchParams } from 'react-router';
import './App.css';
import { Pagination } from './components/Pagination';
import { getNumbers } from './utils';
@@ -13,13 +15,25 @@ enum PerPage {
const items: string[] = getNumbers(1, 42).map(n => `Item ${n}`);
export const App: React.FC = () => {
- const [page, setPage] = useState(1);
- const [perPage, setPerPage] = useState(PerPage.FIVE);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const page = Number(searchParams.get('page')) || 1;
+ const perPage = Number(searchParams.get('perPage')) || PerPage.FIVE;
const startIndex = (page - 1) * perPage;
const endIndex = Math.min(startIndex + perPage, items.length);
const visibleItems = items.slice(startIndex, endIndex);
+ const handlePageChange = (newPage: number) => {
+ searchParams.set('page', newPage.toString());
+ setSearchParams(searchParams);
+ };
+
+ const handlePerPageChange = (e: React.ChangeEvent) => {
+ searchParams.set('perPage', e.target.value);
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ };
+
return (
Items with Pagination
@@ -35,10 +49,7 @@ export const App: React.FC = () => {
id="perPageSelector"
className="form-control"
value={perPage}
- onChange={e => {
- setPerPage(Number(e.target.value));
- setPage(1);
- }}
+ onChange={handlePerPageChange}
>
@@ -56,11 +67,7 @@ export const App: React.FC = () => {
total={items.length}
perPage={perPage}
currentPage={page}
- onPageChange={(selectedPage: number) => {
- if (page !== selectedPage) {
- setPage(selectedPage);
- }
- }}
+ onPageChange={handlePageChange}
/>
{visibleItems.map(item => {
diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx
index 0fb15487a..6d19e543c 100644
--- a/src/components/Pagination/Pagination.tsx
+++ b/src/components/Pagination/Pagination.tsx
@@ -3,7 +3,7 @@ import React from 'react';
interface Props {
total: number;
perPage: number;
- currentPage: number;
+ currentPage?: number;
onPageChange: (selectedPage: number) => void;
}
@@ -54,7 +54,9 @@ export const Pagination: React.FC = ({
href={`#${num}`}
onClick={e => {
e.preventDefault();
- onPageChange(num);
+ if (num !== currentPage) {
+ onPageChange(num);
+ }
}}
>
{num}
diff --git a/src/index.tsx b/src/index.tsx
index 226f3f4bd..d960d5966 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,11 @@
import { createRoot } from 'react-dom/client';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { BrowserRouter } from 'react-router';
import { App } from './App';
-createRoot(document.getElementById('root') as HTMLElement).render();
+createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+ ,
+);