Applications that display large amounts of data in the form of either a list or table can implement pagination to divide the data into a sequence of pages. Instead of fetching huge amounts of data from a server API, pagination loads data on demand, decreasing load time and improving the overall UI.
While there are many libraries available for implementing pagination, some are a bit heavy, harming your app’s overall performance. In this article, we’ll create a reusable pagination component using React Hooks.
As an example, we’ll display the passenger list in the image below below using pagination. The complete code is available at the react-pagination GitHub repository; please feel free to fork it:
useState
HookuseEffect
HookBefore jumping into the implementation, let me introduce two React Hooks that we’ll use in this article. If you’re already familiar with React Hooks, feel free to skip to the tutorial.
useState
HookIf you’d like to add state to your functional component, the useState
Hook will do the job. Consider the example below:
// a new state variable called "currentPage" const [currentPage, setCurrentPage] = useState(1);
The useState
Hook helps to preserve the values between function calls. It takes a single argument, which is the initial state. In the example above, the currentPage
variable is set to 1
by default. The useState
Hook will return a pair of values, the first being the variable’s state, and the second is the function to set the state.
We’ll use the useState
Hook to maintain the state for the current page, the minimum and maximum page limits, as well as hold the response data from the passenger API.
useEffect
HookIf you’d like to perform any side effects like data fetching, DOM manipulation, or subscribing and unsubscribing to events, the useEffect
Hook will help you:
useEffect(()=>{ // call rest api },[]);
React has access to the state of your component, so it will essentially remember the function you passed and invoke it after DOM updates. We use the useEffect
Hook to fetch passenger data from the REST API.
Our goal is to display the passenger list with pagination, so we’ll use the free REST API to provide a list of passenger data. Begin by creating a new React application using the Create React App command below:
npx create-react-app pagination-tutorial
Remove all the default content from the App.js
file. We’ll include our components here later. The final file should look like the code below:
import './App.css'; function App() { return ( <div className="App"> <div className="App-header"> // include the Passenger componnet, which we create by end of the article </div> </div> ); } export default App;
Now, we can run the application with the command below:
yarn start
The functional component allows you to split the UI into independent and reusable pieces. We’ll use a functional component that has a single argument holding props to the component. Before creating our pagination component, let’s clarify a few of the few terms used throughout the rest of the article.
Jump to the src
folder and create a new file called Pagination.js
:
import React from 'react'; const Pagination = (props)=>{ return( <div> </div> ) } export default Pagination;
Since we’re creating a reusable component for pagination, assume that the component gets all inputs from the parent component using props. We aim to build an output like the screenshot below:
Initialize the terms we discussed in previous section:
// init const { currentPage, maxPageLimit, minPageLimit} = props; const totalPages = props.response.totalPages-1; const data = props.response.data;
Now, to input the total number of pages, we’ll build page numbers starting from 1
. The limit will go based on maximum and minimum page limits:
Build the pages array, which will hold all the numbers ranging from 1
to total pages:
// build page numbers list based on total number of pages const pages = []; for(let i=1 ; i<=totalPages; i++){ pages.push(i); }
To create the UI for all page numbers that fall within the maximum and minimum page limits, iterate through the pages array built with the step above. If the iteration page matches with the current page, make it active:
const pageNumbers = pages.map(page => { if(page <= maxPageLimit && page > minPageLimit) { return( <li key={page} id={page} onClick={handlePageClick} className={currentPage===page ? 'active' : null}> {page} </li> ); }else{ return null; } } );
Next, we’ll create ellipsis dots …
, which appear when there are more pages than the current page on either the left or right. On clicking the ellipsis, the user can move to either the previous or the next group of pages. You can see these in the image below, highlighted in green:
// page ellipses let pageIncrementEllipses = null; if(pages.length > maxPageLimit){ pageIncrementEllipses = <li onClick={handleNextClick}>…</li> } let pageDecremenEllipses = null; if(minPageLimit >=1){ pageDecremenEllipses = <li onClick={handlePrevClick}>…</li> } const renderData = (data)=>{
Let’s render our passengers in a list format. In the code below, I’m showing the ID of the passenger and the name of the airline used by each passenger:
return( <ul> {data.map((d)=> <li key={d['_id']}> The passenger having id {d['_id'].slice(d['_id'].length-5)} using {d.airline[0].name} airlines</li>) } </ul> ) }
Next, we’ll combine all of the UI elements and return them in our functional component along with the Prev and Next buttons.
On clicking Prev, the user navigates to the previous page. On clicking Next, the user navigates to the next page. The UI elements are aligned as follows:
1. Passenger’s data 2. Pager 1. Prev button 2. Page decrement ellipses 3. Page numbers with current page selection 4. Page increment ellipses 5. Next button return ( <div className="main"> <div className="mainData"> {renderData(data)} </div> <ul className="pageNumbers"> <li> <button onClick={handlePrevClick} disabled={currentPage === pages[0]}>Prev</button> </li> {pageDecremenEllipses} {pageNumbers} {pageIncrementEllipses} <li> <button onClick={handleNextClick} disabled={currentPage === pages[pages.length-1]}>Next</button> </li> </ul> </div> )
So far, we created the UI, which includes events like page click, prev click, and next click. Now, let’s bind all the events:
const handlePrevClick = ()=>{ props.onPrevClick(); } const handleNextClick = ()=>{ props.onNextClick(); } const handlePageClick = (e)=>{ props.onPageChange(Number(e.target.id)); }
Now, we’ll navigate to the parent component, which is the Passengers
component in our case, responsible for fetching passenger data and setting the pagination props. Use the Pagination
component created above in this component.
To create the Passengers
component, first, initialize the state. You can refer to the state variables below. In simple terms, we’ll show page numbers starting from 1
to 5
at the start of our pagination component. We initialize the minimum and maximum page limits with 0
and 5
, respectively.
We set the current page to 1
, meaning that the user will be on the first page. By setting the passenger data to an empty array, we determine that we’ll fetch the data in the next steps. The page number limit, 5
, tells us that we’ll show only five page numbers on UI:
const pageNumberLimit = 5; const [passengersData, setData] = useState([]); const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState(1); const [maxPageLimit, setMaxPageLimit] = useState(5); const [minPageLimit, setMinPageLimit] = useState(0);
When the current page changes, we’ll fetch the passenger data from our API with the useEffect
Hook, persisting it in the passengersData
state variable:
useEffect(()=>{ setLoading(true); fetch(`https://api.instantwebtools.net/v1/passenger?currentPage=${currentPage}&size=5`) .then((response) => response.json()) .then((json) => { setData(json); setLoading(false);}); },[currentPage]);
The core step of this complete pagination is updating the maximum, minimum, and current page, which are updated on the page change, previous click, and next click.
Assume you are on the sixth page of our pagination component. If you click the Prev button, you’ll be directed to the fifth page, and the page limits should be reset to 1
and 5
.
To achieve the example above, we’ll update the maximum and minimum page limits only if the previous page is not in the current page range. The remainder obtained after dividing the previous page with page number should be zero.
For example, consider that the maximum page limit should equal the current max limit minus the page number limit. In our example in the picture above, 10 minus five equals five.
For the minimum page limit, we subtract the page number limit from the current minimum limit. In our example, we calculate that six minus five equals one. Keep in mind that the current page is the previous page minus 1
.
We’ll explore the next click as a toggle of the previous example. Assume you’re on the fifth page now. Click Next, and you should navigate to the sixth page. The page limits should be between 6
and 10
.
Like with the previous click, we can only achieve the scenario above by updating the maximum and minimum page limits. Maximum and minimum page limits are updated only when the next page is greater than the current max page limit:
To calculate the maximum page limit, we add the current maximum limit to the page number limit. In our example, we add five and five to equal 10.
The minimum page limit is the current minimum limit plus the page number limit. In our example, one plus five equals six. The current page is the previous page plus one.
On click of any page number, update the current page to the specific number. Below is the code snippet for all major events:
const onPageChange= (pageNumber)=>{ setCurrentPage(pageNumber); } const onPrevClick = ()=>{ if((currentPage-1) % pageNumberLimit === 0){ setMaxPageLimit(maxPageLimit - pageNumberLimit); setMinPageLimit(minPageLimit - pageNumberLimit); } setCurrentPage(prev=> prev-1); } const onNextClick = ()=>{ if(currentPage+1 > maxPageLimit){ setMaxPageLimit(maxPageLimit + pageNumberLimit); setMinPageLimit(minPageLimit + pageNumberLimit); } setCurrentPage(prev=>prev+1); }
To create the UI for passengers with pagination, we’ll combine all the attributes needed and send them as props to the pagination component:
const paginationAttributes = { currentPage, maxPageLimit, minPageLimit, response: passengersData, }; return( <div> <h2>Passenger List</h2> {!loading ? <Pagination {...paginationAttributes} onPrevClick={onPrevClick} onNextClick={onNextClick} onPageChange={onPageChange}/> : <div> Loading... </div> } </div> )
Now, we’ve built both parent and child components. You are ready to use the parent component anywhere in the application.
In this tutorial, we created a pagination component by sending the current page, maximum page limit, minimum page limit, and other page events as props to the component. We used React Hooks for state management and fetching data.
While we developed our example application in React, you can apply the same logic to any framework. I hope you learned something new! Be sure to leave a comment if you have any questions. Happy coding!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
3 Replies to "React pagination from scratch using Hooks"
Hi, This is great article about pagination.
What if, we wanted to show the button to show last page and first page
Hi, Although the approach for creating pagination is great and easy. I’m bit concerned about what will happen if current page won’t load then we will end up showing current page on UI but with the old data.
IMHO It would be great if we just set current page once page data is fetched. and in next and previous calls we just increment and decrement current page with a local variable.
There is bug with the code on github
fetch(`https://api.instantwebtools.net/v1/passenger?page=${currentPage}&size=5`) instead of
fetch(`https://api.instantwebtools.net/v1/passenger?currentPage=${currentPage}&size=5`)
in teh file Passenger.js