20
loading...
This website collects cookies to deliver better user experience
create-react-app
and json
placeholder API ( a superb API for mockups).App.js
looks like this. It follows the outline we described above. It fetches the data using useEffect
hooks and passes it down to our custom Pagination Component with few state constants.// App.js
import React, { useState, useEffect } from "react";
import Pagination from "./components/Pagination";
import Post from "./components/Post";
const url = "https://jsonplaceholder.typicode.com/posts";
function App() {
const [posts, setPosts] = useState([]);
const [error, setError] = useState("");
useEffect(() => {
fetch(url)
.then((response) => {
if (response.ok) return response.json();
throw new Error("something went wrong while requesting the data");
})
.then((posts) => {
setPosts(posts);
})
.catch((error) => setError(error.message));
}, []);
if (error) return <h1>{error}</h1>;
return (
<div>
{posts.length > 0 ? (
<>
<Pagination
data={posts}
RenderComponent={Post}
title="Posts"
buttonConst={3}
contentPerPage={5}
siblingCount={1}
/>
</>
) : (
<h1>No Posts to display</h1>
)}
</div>
);
}
export default App;
usePaginationRange
hook. It will take props and essentially spits out an array of value based on currentPage .Please see the code for it below. I highlighted the comments with a little explanation.//Pagination.js
import React, { useState, useEffect } from "react";
import { usePaginationRange, DOTS } from "../hooks/usePaginationRange";
const Pagination = ({
data,
RenderComponent,
title,
buttonConst,
contentPerPage,
siblingCount,
}) => {
const [totalPageCount] = useState(Math.ceil(data.length / contentPerPage));
const [currentPage, setCurrentPage] = useState(1);
const paginationRange = usePaginationRange({
totalPageCount,
contentPerPage,
buttonConst,
siblingCount,
currentPage,
});
/* 👇 little UX tweak when user clicks on any button we scoll to top of the page */
useEffect(() => {
window.scrollTo({
behavior: "smooth",
top: "0px",
});
}, [currentPage]);
function goToNextPage() {
setCurrentPage((page) => page + 1);
}
function gotToPreviousPage() {
setCurrentPage((page) => page - 1);
}
function changePage(event) {
const pageNumber = Number(event.target.textContent);
setCurrentPage(pageNumber);
}
const getPaginatedData = () => {
const startIndex = currentPage * contentPerPage - contentPerPage;
const endIndex = startIndex + contentPerPage;
return data.slice(startIndex, endIndex);
};
return (
<div>
<h1>{title}</h1>
{/* show the post 10 post at a time*/}
<div className="dataContainer">
{getPaginatedData().map((dataItem, index) => (
<RenderComponent key={index} data={dataItem} />
))}
</div>
{/* show the pagiantion
it consists of next and previous buttons
along with page numbers, in our case, 5 page
numbers at a time */}
<div className="pagination">
{/* previous button */}
<button
onClick={gotToPreviousPage}
className={` prev ${currentPage === 1 ? "disabled" : ""}`}
>
previous
</button>
{/* show paginated button group */}
{paginationRange.map((item, index) => {
if (item === DOTS) {
return (
<button key={index} className={`paginationItem`}>
…
</button>
);
}
return (
<button
key={index}
onClick={changePage}
className={`paginationItem ${
currentPage === item ? "active" : null
}`}
>
<span>{item}</span>
</button>
);
})}
{/* next button */}
<button
onClick={goToNextPage}
className={`next ${currentPage === totalPageCount ? "disabled" : ""}`}
>
next
</button>
</div>
</div>
);
};
export default Pagination;
usePaginateRange
hook ://usePaginationRange.js
import { useMemo } from "react";
export const DOTS = "...";
// our range generator function
const range = (start, end) => {
let length = end - start + 1;
return Array.from({ length }, (_, index) => index + start);
};
export const usePaginationRange = ({
totalPageCount,
dataLimit,
buttonConst,
siblingCount,
currentPage,
}) => {
const paginationRange = useMemo(() => {
// Pages count is determined as siblingCount + buttonConst(firstPage + lastPage + currentPage) + 2*DOTS
const totalPageNumbers = buttonConst + 2 + siblingCount;
/*
If the number of pages is less than the page numbers we want to show in our
paginationComponent, we return the range [1..totalPageCount]
*/
if (totalPageNumbers >= totalPageCount) {
return range(1, totalPageCount);
}
/*
Calculate left and right sibling index and make sure they are within range 1 and totalPageCount
*/
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(
currentPage + siblingCount,
totalPageCount
);
/*
We do not want to show dots if there is only one position left
after/before the left/right page count as that would lead to a change if our Pagination
component size which we do not want
*/
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex <= totalPageCount - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPageCount;
/*
No left dots to show, but rights dots to be shown
*/
if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingCount;
let leftRange = range(1, leftItemCount);
return [...leftRange, DOTS, totalPageCount];
}
/*
No right dots to show, but left dots to be shown
*/
if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 * siblingCount;
let rightRange = range(
totalPageCount - rightItemCount + 1,
totalPageCount
);
return [firstPageIndex, DOTS, ...rightRange];
}
/*
Both left and right dots to be shown
*/
if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = range(leftSiblingIndex, rightSiblingIndex);
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
}
}, [totalPageCount, siblingCount, currentPage, buttonConst]);
return paginationRange;
};
useEffect(() => {
window.scrollTo({
behavior: "smooth",
top: "0px",
});
}, [currentPage]);