ReactJS - Pagination

Pagination is one of the distinct features requiring special logic and at the same time, the UI rendering may vary drastically. For example, the pagination can be done for a tabular content as well as a gallery content. Pagination component will do the pagination logic and delegates the rendering logic to other component. Let us try to create a pagination component (Pager) for our Expense manager application using Render props
Open expense-manager application in your favorite editor.
Next, create a file, Pager.css under src/components to add styles for pagination component.
a{
   text-decoration: none;
}
p, li, a{
   font-size: 12px;
} 
.container{
   width: 720px;
   max-width: 340px;
   margin: 0 auto;
   position: relative;
   text-align: center;
}
.pagination{
   padding: 14px 0;
}
.pagination ul{
   margin: 0 auto; 
   padding: 0;
   list-style-type: none;
   text-align: left;
}
.pagination a{
   display: inline-block;
   padding: 10px 18px;
   color: #222;
}
.p1 a{
   width: 30px;
   height: 30px;
   line-height: 30px;
   padding: 0;
   text-align: center;
}
.p1 a.is-active{
   background-color: #887cb8;
   border-radius: 25%;
   color: #fff;
}
Next, create a file, Pager.js in src/components folder to create Pager component and start editing.
Next, import React library.
import React from 'react';

Next, import pagination stylesheet.
import './Pager.css'

Next, create a class, Pager and call constructor with props.
class Pager extends React.Component { 
   constructor(props) { 
      super(props); 
   } 
}

Next, initialize the state of the component with expense items (items) and number of items to show in a page (pageCount).
this.state = { 
   items: this.props.items, 
   pageCount: this.props.pageCount 
}

Write a function, calculate(), which accepts current state and the current page number. It calculates the total of available pages based on the page count and the items to show in current page (filteredItems). Finally, it returns the computed values.
calculate(state, pageNo) {
   let currentPage = pageNo;
   let totalPages = Math.ceil(state.items.length / state.pageCount);
   if(currentPage > totalPages) currentPage = totalPages;
   let hasPreviousPage = currentPage == 1 ? false : true;
   let hasNextPage = currentPage == totalPages ? false : true;
   let first = (currentPage - 1) * state.pageCount
   let last = first + state.pageCount;
   let filteredItems = state.items.slice(first, last)

   let newState = {
      items: state.items,
      filteredItems: filteredItems,
      currentPage: currentPage,
      totalPages: totalPages,
      pageCount: state.pageCount
   }
   return newState;
}
Next, call the calculate() method in the constructor to get the initial details about the pagination.
this.state = this.calculate(this.state, 1);

Next, write an event handler method, handleClick to handle the page navigation event of the pagination component.
handleClick(pageNo, e) { 
   e.preventDefault(); 
   this.setState((state, props) => { 
      return this.calculate(state, pageNo); 
   }) 
}

Next, write an event handler to remove the expense item. Since, the pager component handles all the expense items, the remove feature should be moved here.
handleDelete = (id, e) => {
   e.preventDefault();
   console.log(id);
   this.setState((state, props) => {
      let items = [];

      state.items.forEach((item, idx) => {
         if (item.id != id)
            items.push(item)
      })
      let newState = {
         items: items
      }
      return newState;
   })
   this.setState((state, props) => {
      return this.calculate(state, this.state.currentPage);
   })
}
Next, create a render method to create the pagination user interface and then call the Render Props by passing the items needed to render for the current page.
render() {
   let pageArray = new Array();
   let i = 1;
   for (i = 1; i <= this.state.totalPages; i++)
      pageArray.push(i)

   const pages = pageArray.map((idx) =>
      <a href="#" key={idx} onClick={this.handleClick.bind(this, idx)} className={idx == this.state.currentPage ? "is-active" : ""}><li>{idx}</li></a>
   );
   let propsToPass = {
      items: this.state.filteredItems,
      deleteHandler: this.handleDelete
   }
   return (
      <div>
         {this.props.render(propsToPass)}
         <div style={{ width: 720, margin: 0 }}>
            <div className="container">
               <div className="pagination p1">
                  <ul>
                     {this.state.currentPage != 1 ? <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage - 1)}><li><</li></a> : <span> </span>}
                     {pages}
                     {this.state.currentPage != this.state.totalPages ?
                     <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage + 1)}><li>></li></a> : <span> </span>}
                  </ul>
               </div>
            </div>
         </div>
      </div>
   )
}
Here,
Used map to create the pagination buttons.
Used map to create the pagination buttons.
Used conditional rendering to show/hide first and last page.
Used conditional rendering to show/hide first and last page.
Used this.props.render to call and render the passed in component.
Used this.props.render to call and render the passed in component.
Finally, Export the component.
export default Pager

The complete code of the Pager component is as follows
import React from 'react';
import './Pager.css'

class Pager extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         items: this.props.items,
         pageCount: this.props.pageCount,
      }
      this.state = this.calculate(this.state, 1);
   }
   calculate(state, pageNo) {
      let currentPage = pageNo;
      let totalPages = Math.ceil(state.items.length / state.pageCount);
      if(currentPage > totalPages) currentPage = totalPages;
      let hasPreviousPage = currentPage == 1 ? false : true;
      let hasNextPage = currentPage == totalPages ? false : true;
      let first = (currentPage - 1) * state.pageCount
      let last = first + state.pageCount;
      let filteredItems = state.items.slice(first, last)
      let newState = {
         items: state.items,
         filteredItems: filteredItems,
         currentPage: currentPage,
         totalPages: totalPages,
         pageCount: state.pageCount
      }
      return newState;
   }
   handleClick(pageNo, e) {
      e.preventDefault();
      this.setState((state, props) => {
         return this.calculate(state, pageNo);
      })
   }
   handleDelete = (id, e) => {
      e.preventDefault();
      console.log(id);

      this.setState((state, props) => {
         let items = [];
         state.items.forEach((item, idx) => {
            if (item.id != id)
               items.push(item)
         })
         let newState = {
            items: items
         }
         return newState;
      })
      this.setState((state, props) => {
         return this.calculate(state, this.state.currentPage);
      })
   }
   render() {
      let pageArray = new Array();
      let i = 1;
      for (i = 1; i <= this.state.totalPages; i++)pageArray.push(i)
      
      const pages = pageArray.map((idx) =>
         <a href="#" key={idx} onClick={this.handleClick.bind(this, idx)} className={idx == this.state.currentPage ? "is-active" : ""}><li>{idx}</li></a>
      );
      let propsToPass = {
         items: this.state.filteredItems,
         deleteHandler: this.handleDelete
      }
      return (
         <div>
            {this.props.render(propsToPass)}
   
            <div style={{ width: 720, margin: 0 }}>
               <div className="container">
                  <div className="pagination p1">
                     <ul>
                        {this.state.currentPage != 1 ? <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage - 1)}><li><</li></a> : <span> </span>}
                        {pages}
                        {this.state.currentPage != this.state.totalPages ?
                        <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage + 1)}><li>></li></a> : <span> </span>}
                     </ul>
                  </div>
               </div>
            </div>
         </div>
      )
   }
}
export default Pagerimport React from 'react';
import './Pager.css'

class Pager extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         items: this.props.items,
         pageCount: this.props.pageCount,
      }
      this.state = this.calculate(this.state, 1);
   }
   calculate(state, pageNo) {
      let currentPage = pageNo;
      let totalPages = Math.ceil(state.items.length / state.pageCount);

      if(currentPage > totalPages)currentPage = totalPages;
         
      let hasPreviousPage = currentPage == 1 ? false : true;
      let hasNextPage = currentPage == totalPages ? false : true;
      let first = (currentPage - 1) * state.pageCount
      let last = first + state.pageCount;
      let filteredItems = state.items.slice(first, last)
   
      let newState = {
         items: state.items,
         filteredItems: filteredItems,
         currentPage: currentPage,
         totalPages: totalPages,
         pageCount: state.pageCount
      }
      return newState;
   }
   handleClick(pageNo, e) {
      e.preventDefault();
   
      this.setState((state, props) => {
         return this.calculate(state, pageNo);
      })
   }
   handleDelete = (id, e) => {
      e.preventDefault();
      console.log(id);

      this.setState((state, props) => {
         let items = [];

         state.items.forEach((item, idx) => {
            if (item.id != id)
               items.push(item)
         })
         let newState = {
            items: items
         }
         return newState;
      })
      this.setState((state, props) => {
         return this.calculate(state, this.state.currentPage);
      })
   }
   render() {
      let pageArray = new Array();
      let i = 1;
      for (i = 1; i <= this.state.totalPages; i++)pageArray.push(i)
   
      const pages = pageArray.map((idx) =>
         <a href="#" key={idx} onClick={this.handleClick.bind(this, idx)} className={idx == this.state.currentPage ? "is-active" : ""}><li>{idx}</li></a>
      );

      let propsToPass = {
         items: this.state.filteredItems,
         deleteHandler: this.handleDelete
      }
      return (
         <div>
            {this.props.render(propsToPass)}
   
            <div style={{ width: 720, margin: 0 }}>
               <div className="container">
                  <div className="pagination p1">
                     <ul>
                        {this.state.currentPage != 1 ? <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage - 1)}><li><</li></a> : <span> </span>}
                        {pages}
                        {this.state.currentPage != this.state.totalPages ?
                        <a href="#" onClick={this.handleClick.bind(this, this.state.currentPage + 1)}><li>></li></a> : <span> </span>}
                     </ul>
                  </div>
               </div>
            </div>
         </div>
      )
   }
}
export default Pager
Next, open ExpenseEntryItemeList.js file and update the getDerivedStateFromProps function to get the current expense item list by copying the passed-in props into the new state.
static getDerivedStateFromProps(props, state) {
   let newState = {
      items: props.items
   }
   return newState;
}

Next, update the handleDelete method and call the event handler method passed into the component from from pager component through onDelete property.
handleDelete = (id, e) => {
   e.preventDefault();

   if(this.props.onDelete != null)this.props.onDelete(id, e);
}

Next, remove rendering of header and footer, if any available in the render method.
return (
   <div>
      <div>{this.props.header}</div>
         ... existing code ...
      <div>{this.props.footer}</div>
   </div>
);

The complete source code of the ExpenseEntryItemList component is given below −
import React from 'react';
import './ExpenseEntryItemList.css';

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         items: this.props.items
      }
      this.handleMouseEnter = this.handleMouseEnter.bind();
      this.handleMouseLeave = this.handleMouseLeave.bind();
      this.handleMouseOver = this.handleMouseOver.bind();
   }
   handleMouseEnter(e) {
      e.target.parentNode.classList.add("highlight");
   }
      handleMouseLeave(e) {
      e.target.parentNode.classList.remove("highlight");
   }
   handleMouseOver(e) {
      console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
   }
   handleDelete = (id, e) => {
      e.preventDefault();

      if(this.props.onDelete != null)this.props.onDelete(id, e);
         
      /*console.log(id);

      this.setState((state, props) => {
         let items = [];
         state.items.forEach((item, idx) => {
            if (item.id != id)
               items.push(item)
         })
         let newState = {
            items: items
         }
         return newState;
      })*/
   }
   getTotal() {
      let total = 0;
      for (var i = 0; i < this.state.items.length; i++) {
         total += this.state.items[i].amount
      }
      return total;
   }
   static getDerivedStateFromProps(props, state) {
      let newState = {
         items: props.items
      }
      return newState;
   }
   render() {
      const lists = this.state.items.map((item) =>
         <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
            <td>{item.name}</td>
            <td>{item.amount}</td>
            <td>{new Date(item.spendDate).toDateString()}</td>
            <td>{item.category}</td>
            <td><a href="#"
               onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
         </tr>
      );
      return (
         <div>
            <table onMouseOver={this.handleMouseOver}>
               <thead>
                  <tr>
                     <th>Item</th>
                     <th>Amount</th>
                     <th>Date</th>
                     <th>Category</th>
                     <th>Remove</th>
                  </tr>
               </thead>
               <tbody>
                  {lists}
                  <tr>
                     <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
                     <td colSpan="4" style={{ textAlign: "left" }}>
                        {this.getTotal()}
                     </td>
                  </tr>
               </tbody>
            </table>
         </div>
      );
   }
}
export default ExpenseEntryItemList;
Next, open index.js and call the Pager component and pass the ExpenseEntryItemList as render props.
import React from 'react';
import ReactDOM from 'react-dom';
import Pager from './components/Pager'
import ExpenseEntryItemList from './components/ExpenseEntryItemList'

const items = [
   { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
   { id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
   { id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
   { id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
   { id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
   { id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
   { id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
   { id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
   { id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
   { id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
const pageCount = 3;
ReactDOM.render(
   <React.StrictMode>
      <Pager
         items={items}
         pageCount={pageCount}
         render={
            pagerState => (
               <div>
                  <ExpenseEntryItemList items={pagerState.items} 
                        onDelete={pagerState.deleteHandler} />
               </div>
            )
         }
      />
   </React.StrictMode>,
   document.getElementById('root')
);

As we see, the Pager component accept any render props and only calculate the pagination logic.
Next, serve the application using npm command.
npm start

Next, open the browser and enter http://localhost:3000 in the address bar and press enter.