I am working on a site where I am trying to display paginated student information. For example I have 12 students I have pagination items 1-4 as I display 3 students at a time.
I am running into a problem when I call my update pagination function. The counter in this method is always 0 despite me incrementing it correctly. Any ideas or pointers would be greatly appreciated.
import React, {useState, useEffect} from 'react';
function App() {
let students = [
{'name': 'Harry'},
{'name': 'Hermoine'},
{'name': 'Ron'},
{'name': 'Ginny'},
{'name': 'Snape'},
{'name': 'Bellatrix'},
{'name': 'Albus'},
{'name': 'Dudley'},
{'name': 'Petunia'},
{'name': 'Hagrid'},
{'name': 'Lee'},
{'name': 'James'}
];
let [loaded, setLoaded] = useState(false);
let [pagination, setPagination] = useState([]);
let [limit, setLimit] = useState(3); // how many students are visible at a time
let [pages, setPages] = useState(Math.round(students.length/limit)); // amount of pages based on total students
let [currentPage, setCurrentPage] = useState(1); // the current page
let [pageCount, setPageCount] = useState(0); // counter used to increment/decrement pages in view
function updatePagination() {
let tmpArray = [];
for(let i = pageCount; i < pages; i ++){
tmpArray.push(i+1);
}
// 1-3, 4-6 etc...
setPagination(tmpArray.slice(pageCount, pageCount + limit)); // this pageCount is always 0 despite me setting it on handleNext method
}
function handleNext(){
setCurrentPage(currentPage + 1);
if(pageCount + limit === currentPage){
if(currentPage <= pages){
setPageCount(pageCount + limit);
}
}
updatePagination();
}
useEffect(() => {
updatePagination();
setLoaded(true);
}, []);
return (
<main className={styles.app}>
<div className={styles.student}>
<h1>Three student cards</h1>
</div>
<ol className={styles.pagination}>
<div className={styles.paginationContainer}>
<button className={currentPage === 0 ? styles.prev + ' ' + styles.disabled : styles.prev}>prev</button>
{loaded && pagination.map((item, index) => {
return (
<li key={index} className={styles.paginationItem}>
<button className={currentPage === item ? styles.active : null}>{item}</button>
</li>
)
})}
<button onClick={() => {
handleNext()
}} className={currentPage === pages ? styles.next + ' ' + styles.disabled : styles.next}>next</button>
</div>
</ol>
{loaded &&
<div className={styles.info}>
<p>Page Count: {pageCount}</p>
<p>Current Page: {currentPage}</p>
<p>Limit: {limit}</p>
<p>Pages: {pages}</p>
<p>Total Students: {students.length}</p>
<p>Pagination: {pagination}</p>
</div>
}
</main>
);
}
export default App;
Following are the problems in your code:
Inside handleNext()
function, you are using currentPage
immediately after calling setCurrentPage(...)
function BUT the state is updated asynchronously. So currentPage
will be the old value instead of the updated value.
Another problem is that if the user clicks the next
button three times, then the conditon pageCount + limit === currentPage
will be true
and pageCount
will be set to pageCount + limit
which is 3. This means that the loop inside handleNext()
function for (let i = pageCount; i < pages; i++) {...}
will only execute once. So tmpArray
will contain only one element, ie 4.
Also, since calling handleNext()
function updates the currentPage
depending on its previous value, you should update the state by passing the function to setCurrentPage()
function
setCurrentPage((page) => page + 1);
Similarly to go to previous page:
setCurrentPage((page) => page - 1);
This will make sure that state is updated correctly.
It wasn't clear before why you were using pageCount
but the demo you posted in a comment made it clear what you are trying to achieve.
Problem you mentioned in your question is that value of pageCount
is always zero. Looking at your code, i think you don't need pageCount
at all.
To achieve the desired functionality, you need to take following steps:
To populate the pagination
array, you can make use of the useEffect
hook that executes whenever students
array or limit
changes.
useEffect(() => { // set pagination let arr = new Array(Math.ceil(students.length / limit)).fill().map((_, idx) => idx + 1); setPagination(arr); setLoaded(true); }, [students, limit]);
To display limited number of students at a time, create a function that slices the students
array depending on the currentPage
and limit
.
const getPaginatedStudents = () => { const startIndex = currentPage * limit - limit; const endIndex = startIndex + limit; return students.slice(startIndex, endIndex); };
To display a limited number of pages equal to the limit
, you don't need pageCount
. You can create a function that slices the pagination
array depending on the currentPage
and limit
. This function is similar to the one created in step 2 but the difference lies in how startIndex
is calculated.
const getPaginationGroup = () => { let start = Math.floor((currentPage - 1) / limit) * limit; let end = start + limit; return pagination.slice(start, end); };
Create functions that will change the currentPage
function goToNextPage() { setCurrentPage((page) => page + 1); } function goToPreviousPage() { setCurrentPage((page) => page - 1); } function changePage(event) { const pageNumber = Number(event.target.textContent); setCurrentPage(pageNumber); }
That's all you need to get the desired functionality.
Following code snippet shows an example:
const studentsData = [ { name: "Harry" }, { name: "Hermoine" }, { name: "Ron" }, { name: "Ginny" }, { name: "Snape" }, { name: "Bellatrix" }, { name: "Albus" }, { name: "Dudley" }, { name: "Petunia" }, { name: "Hagrid" }, { name: "Lee" }, { name: "James" }, { name: "Lily" }, { name: "Remus" }, { name: "Voldemort" }, { name: "Dobby" }, { name: "Lucius" }, { name: "Sirius" } ]; function Student({ name }) { return ( <div className="student"> <h3>{name}</h3> </div> ); } function App() { let [students] = React.useState(studentsData); let [pagination, setPagination] = React.useState([]); let [loaded, setLoaded] = React.useState(false); let [limit] = React.useState(3); let [pages] = React.useState(Math.round(students.length / limit)); let [currentPage, setCurrentPage] = React.useState(1); function goToNextPage() { setCurrentPage((page) => page + 1); } function goToPreviousPage() { setCurrentPage((page) => page - 1); } function changePage(event) { const pageNumber = Number(event.target.textContent); setCurrentPage(pageNumber); } React.useEffect(() => { // set pagination let arr = new Array(Math.ceil(students.length / limit)).fill().map((_, idx) => idx + 1); setPagination(arr); setLoaded(true); }, [students, limit]); const getPaginatedStudents = () => { const startIndex = currentPage * limit - limit; const endIndex = startIndex + limit; return students.slice(startIndex, endIndex); }; const getPaginationGroup = () => { let start = Math.floor((currentPage - 1) / limit) * limit; let end = start + limit; return pagination.slice(start, end); }; return ( <main> <h1>Students</h1> {loaded && ( <div className="studentsContainer"> {getPaginatedStudents().map((s) => ( <Student key={s.name} {...s} /> ))} </div> )} <ol className="pagination"> <div className="paginationContainer"> <button onClick={goToPreviousPage} className={currentPage === 1? "prev disabled": "prev"} > prev </button> {loaded && getPaginationGroup().map((item, index) => { return ( <li key={index} className="paginationItem"> <button onClick={changePage} className={currentPage === item? "active": null} > {item} </button> </li> ); })} <button onClick={goToNextPage} className={currentPage === pages? "next disabled": "next"} > next </button> </div> </ol> </main> ); } ReactDOM.render(<App/>, document.getElementById('root'));
h1 { margin: 0; }.studentsContainer { display: flex; background: #efefef; padding: 15px 10px; margin: 5px 0; }.student { flex-grow: 1; box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); text-align: center; max-width: 450px; margin: 0 5px; }.pagination { position: relative; padding: 0; }.paginationContainer { align-items: center; display: grid; grid-template-columns: repeat(auto-fit, minmax(40px, auto)); grid-column-gap: .65rem; justify-content: center; justify-items: center; max-width: 100%; width: 100%; position: relative; }.paginationItem { height: 40px; width: 40px; user-select: none; list-style: none; }.prev, .next { user-select: none; cursor: pointer; background: transparent; border: none; outline: none; }.prev.disabled, .next.disabled { pointer-events: none; opacity: .5; } button { padding: .65rem; display: block; height: 100%; width: 100%; border-radius: 50%; text-align: center; border: 1px solid #ccc; outline: none; cursor: pointer; } button.active { background: rgba(black, .25); border: none; color: #777; pointer-events: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script> <div id="root"></div>
You can also view this demo on codesandbox.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.