3

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;
Yousaf
  • 27,861
  • 6
  • 44
  • 69
Galactic Ranger
  • 851
  • 3
  • 14
  • 30
  • i couldnt run your example in the snadbox can u make a codesnadbox to explain what is the problem? here is the code: https://codesandbox.io/s/adoring-hooks-wlcwm – adir abargil Sep 23 '20 at 08:53
  • Here you go Adir https://codesandbox.io/s/hook-issue-7b57d-7b57d when I click the next button it goes to page 2, page 3 which is great the problem is when it calls the updatePagination function pageCount is always 0 when this method is called despite updating the pageCount on handleNext – Galactic Ranger Sep 23 '20 at 15:34
  • Can you explain the reasoning behind this condition: `pageCount + limit === currentPage` inside `handleNext` function? – Yousaf Sep 23 '20 at 16:01
  • Yousaf users are presentend with the following pagintaion < 1 2 3 > when the user reaches 3 it should update the items to < 4 5 6 > so basically when you reach the last visible item you increase count and use that to slice an array. I have a pagination array that is essentially the total amount of students 1 2 3 4 5 6 7 8 9 10 11 12 when using the next button I increment the count by the limit in this case 3 so uses see 123, then 456 etc... Open to easier solutions or ideas – Galactic Ranger Sep 23 '20 at 16:07

1 Answers1

4

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, i.e. 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.

Edit:

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:

  1. 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]);
    
  2. 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);
    };
    
  3. 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);
    };
    
  4. 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.

Demo

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.

Edit crazy-wescoff-2jttn

Yousaf
  • 27,861
  • 6
  • 44
  • 69
  • Yousaf, I really appreciate your adivce and suggestions. I made a few adjustments based on your feedback and example. You can view my latest version here https://codesandbox.io/s/hook-issue-7b57d-7b57d. The question I had about how to update my page count is still valid. I hard coded the page count an limit in the next and prev handle to clearly convey what I am trying to accomplish. In your example the pagination will display all of the students / limit but in some cases I have many students where displaying them all at once would take up a lot of horizontal space. – Galactic Ranger Sep 23 '20 at 22:03