How to build a custom React pagination component

Mathew Pregasen
Mathew Pregasen
Guest Writer

Oct 27, 2023

Pagination is one of those features that nobody wants to build but is pretty darn critical to a manageable UI design. For those unfamiliar, pagination is when websites break data lists—such as documents or reviews—into pages, complete with a widget to navigate those pages.

Thankfully, building pagination in React applications is doable. Even better, we can create a React pagination component that’s reusable, paginating any arbitrary data and making it easy to plug-and-play in any corner of an application. (For instance, a single pagination component could be used to organize rental listings and listing reviews.)

Let’s explore some pagination strategies using React.

How to implement the usePagination hook

One of the cleanest and most repurposable ways to implement pagination is by creating a React hook. A React hook is a function that enables React’s stateful features to be extended to functional components. Given that many applications are moving away from React classes in favor of simpler functional components, a usePagination hook can be an excellent strategy.

Pagination relies on two inputs—(i) the data entries being paginated and (ii) the maximum amount of entries visible on a single page. With these two numbers, a hook can calculate how many pages should exist and what entries should be attributed to each page.

Keep in mind, React hooks don’t return JSX, but rather stateful variables that our components can use. In the case of hooks, we want four core return values:

  • The current page number
  • The current page data
  • A function to change the page
  • A total amount of pages

Let’s initialize a hook without writing a return function just yet. This starts with a simple code snippet:

1function usePagination(items, pageLimit) {
2}
3export default usePagination;

Next, we can add a single state variable to track the current page (we’ll use a base zero convention for what number denotes each page).

1import React, { useState } from "react";
2
3function usePagination(items, pageLimit) {
4	const [pageNumber, setPageNumber] = useState(0);
5}
6
7export default usePagination;

Now, we can readily start exporting variables, particularly pageNumber and a calculated pageCount.

1import React, { useState } from "react";
2
3function usePagination(items, pageLimit) {
4	const [pageNumber, setPageNumber] = useState(0);
5	const pageCount = Math.ceil(items.length / pageLimit);
6	return { pageNumber, pageCount };
7}
8
9export default usePagination;

And we can now include a changePage function to alter the page:

1import React, { useState } from "react";
2
3function usePagination(items, pageLimit) {
4	const [pageNumber, setPageNumber] = useState(0);
5	const pageCount = Math.ceil(items.length / pageLimit);
6
7	const changePage = (pN) => {
8		setCurrentPage(pN);
9	}
10
11	return { pageNumber, pageCount, changePage };
12}
13
14export default usePagination;

Finally, we just need to include a function to retrieve the data associated with the current page. We can do that by writing a function named pageData with just a few lines of code.

1import React, { useState } from "react";
2
3function usePagination(items, pageLimit) {
4	const [pageNumber, setPageNumber] = useState(0);
5	const pageCount = Math.ceil(items.length / pageLimit);
6
7	const changePage = (pN) => {
8		setPageNumber(pN);
9	}
10
11	const pageData = () => {
12		const s = pageNumber * pageLimit;
13		const e = s + pageLimit;
14		return items.slice(s, e);
15	}
16
17	return { pageNumber, pageCount, changePage, pageData };
18}
19
20export default usePagination;

And there we have it! A usePagination hook. Of course, we could also dress it up with additional helper functions—for instance, something to easily jump to the next or previous page:

1import React, { useState } from "react";
2
3function usePagination(items, pageLimit) {
4  const [pageNumber, setPageNumber] = useState(0);
5  const pageCount = Math.ceil(items.length / pageLimit);
6
7  const changePage = (pN) => {
8    setPageNumber(pN);
9  };
10
11  const pageData = () => {
12    const s = pageNumber * pageLimit;
13    const e = s + pageLimit;
14    return items.slice(s, e);
15  };
16
17  const nextPage = () => {
18    setPageNumber(Math.min(pageNumber + 1, pageCount - 1));
19  };
20
21  const previousPage = () => {
22    setPageNumber(Math.max(pageNumber - 1, 0));
23  };
24
25  return {
26    pageNumber,
27    pageCount,
28    changePage,
29    pageData,
30    nextPage,
31    previousPage,
32  };
33}
34
35export default usePagination;
36

And now we have a fleshed out pagination component!

How to implement the pagination component

We could take our usePagination hook further by creating a reusable pagination component that utilizes the pagination hook and provides a navigation widget for moving around the pages.

Let’s create a new file and a shell pagination component. This component will accept the items, page limit, and a function to return the current page data as props.

1import React, { useEffect } from "react";
2import usePagination from "./usePagination.js";
3
4const Pagination = (props) => {
5  const { pageNumber, changePage, pageData, nextPage, previousPage } =
6    usePagination(props.items, props.pageLimit);
7
8  useEffect(() => {
9    props.setPageItems(pageData);
10  }, [pageNumber]);
11
12  return (
13    <div>
14      <b onClick={previousPage}>Prev</b>
15      <input
16        value={pageNumber}
17        onChange={(e) => {
18          changePage(e.target.valueAsNumber);
19        }}
20        type="number"
21      />
22      <b onClick={nextPage}>Next</b>
23    </div>
24  );
25};
26
27export default Pagination;

The widget provides a button to go to the previous and next page, as well as an input box to skip to an arbitrary page. This allows us to append a pagination navigation below any view. When users interact with the widget, it’ll alter a page data variable, filtering the entire data set with only the data attributed to each page.

How to use the custom pagination component

To use the pagination component, we just need to call it in our app component and also create a state variable for the filtered data. We’ll test it with a very simple array of names.

1import React, { useState } from "react";
2import Pagination from "./Pagination.js";
3
4const App = (props) => {
5  const people = ["Bob", "Lisa", "Anika", "Obi", "Sara"];
6  const pageLimit = 2;
7  const [pagePeople, setPagePeople] = useState([]);
8  return (
9    <div>
10      <li>
11        {pagePeople.map((person, i) => (
12          <ul key={i}>{person}</ul>
13        ))}
14      </li>
15      <Pagination
16        items={people}
17        pageLimit={pageLimit}
18        setPageItems={setPagePeople}
19      />
20    </div>
21  );
22};
23
24export default App;

And just like that, our pagination component works! We’re simply displaying a filtered set of data in our view and using the Pagination component to control that filter. Due to the useEffect embedded in the Pagination function, we don’t even need to calculate the first page data in the parent component.

This strategy can be preferable because it maximizes the flexibility of how we want to view and manipulate the filtered data, and just isolates the page control to the pagination component.

Wrapping up

In this guide, we created a pagination hook and a custom React pagination component, and showcased how to use that reusable component in the greater app. What’s great about this structure is the pagination component’s core logic is secluded to the hook. And given the component is a functional component, it’s lightweight and easy to digest.

We hope this boilerplate code helps you create a React pagination component for your project. On the other hand, if you want to skip the boilerplate, check out Retool’s comprehensive React component library, including a ready-to-go pagination component.

Mathew Pregasen
Mathew Pregasen
Guest Writer
Oct 27, 2023
Copied