Pagination with React Hooks and Material-UI

Pagination with React Hooks and Material-UI

In this demo, we'll set up pagination for a table using React Hooks and Material-UI.

Setup

If you'd like to follow along, fork the starter repository.

Getting Started

Inside of App.jsx, you'll see the two components we're concerned with, MyTable and MyPaginator. We need to accomplish two main tasks. One, use the paginator to filter the data we want to see, and two, display that data on the table. Right now, we're displaying all the data, and the paginator doesn't do anything.

// App.jsx
import React from 'react';
import MyTable from './components/MyTable';
import MyPaginator from './components/MyPaginator';
import MyContainer from './components/MyContainer';
import Heading from './components/Heading';
import data from './assets/MOCK_DATA.json';

const App = () => (
  <MyContainer>
    <Heading />
    <MyTable rows={data} />
    <MyPaginator
      itemCount={data.length}
      itemsPerPage={10}
      onPageChange={() => {}}
      currentPage={1}
      pageCount={5}
    />
  </MyContainer>
);

export default App;

As you can see by the props, we're working with the table rows, the total item count, the total items per page, a function to change the page, the current page number, and the total page count.

Let's start with the easy stuff.

First, I'll create a constant to set the total number of items that will be displayed on each page. This isn't necessary, but neither is most of what we'll be doing. It just makes it easier than digging through the code to change a simple setting.

// etc.
const ITEMS_PER_PAGE = 5;

const App = () => (
// etc.
  itemsPerPage={ITEMS_PER_PAGE}
// etc.
)
//etc.

usePagination Hook

Let's start setting up the hook. You'll find a file called usePagination.jsx in the hooks directory. We could do all of this from the App.jsx file, but one of the good things about hooks is that, in addition to re-usability, we can abstract this logic to another location, leaving our App file a little cleaner.

// usePagination.jsx

import PropTypes from 'prop-types';

const usePagination = (data, itemsPerPage) => {

  return {
    currentPage, getCurrentData, setCurrentPage, pageCount,
  };
};

usePagination.propTypes = {
  data: PropTypes.arrayOf(PropTypes.any.isRequired).isRequired,
  itemsPerPage: PropTypes.number.isRequired,
};

export default usePagination;

We'll be bringing in data and itemsPerPage, then returning currentPage, getCurrentData, setCurrentPage, and pageCount.

Let's jump back to App.jsx to set that up.

  1. Convert App to use curly braces
  2. Import usePagination,
  3. Add the usePagination hook with data and ITEMS_PER_PAGE as its arguments, and destructure the returned object.
  4. Update the props in MyTable and MyPaginator with the items we got from usePagination.

Note that setCurrentPage takes the new page number as an argument. Material-UI provides that as the second parameter in its onPageChange method for the Paginator component.

This should be the last time we have to edit App.jsx.

// App.jsx

import React from 'react';
import MyTable from './components/MyTable';
import MyPaginator from './components/MyPaginator';
import MyContainer from './components/MyContainer';
import Heading from './components/Heading';
import data from './assets/MOCK_DATA.json';
import usePagination from './hooks/usePagination';

const ITEMS_PER_PAGE = 5;

const App = () => {
  const {
    currentPage, getCurrentData, setCurrentPage, pageCount,
  } = usePagination(data, ITEMS_PER_PAGE);

  return (
    <MyContainer>
      <Heading />
      <MyTable rows={getCurrentData()} />
      <MyPaginator
        itemCount={data.length}
        itemsPerPage={ITEMS_PER_PAGE}
        onPageChange={(_, newPage) => setCurrentPage(newPage)}
        currentPage={currentPage}
        pageCount={pageCount}
      />
    </MyContainer>
  );
};

export default App;

Alright, we have our imports, exports, and props set up, but they still don't do anything. Let's move back to usePagination.jsx to get everything working.

We already have data and itemsPerPage from props, but we need two more variables for our state, currentPage and itemCount.

itemCount is just the length of the data array.

currentPage and a function to setCurrentPage will come from the useState hook. This is initialized with a page number of 1.

Don't forget to import useState from react.

import { useState } from 'react';
// etc.

const usePagination = (data, itemsPerPage) => {
  const [currentPage, setCurrentPage] = useState(1);
  const itemCount = data.length;
// etc.
}

The pageCount is created by taking the itemCount, dividing it by the itemsPerPage, then rounding it up to the nearest whole number. So, for example, if the itemCount is 37, the itemsPerPage is 10, we'll get a pageCount of 4.

const pageCount = Math.ceil(itemCount / itemsPerPage);

Finally getCurrentData is where we filter our data down to what we want to see on the current page we're on. That's done by taking a slice of the data where the start is the first item, and the end is the last item.

For start, take the currentPage, subtract 1, then multiply that by itemsPerPage. We subtract 1, because the array starts at the index of 0, but our first page number is 1.

So, if currentPage is 5, subtract 1, giving us an index of 4, multiply that by an itemsPerPage of 10, which gives us a start of 40. data[40] will be our first item.

For end, we just take the start and add itemsPerPage.

So, if start is 40 and itemsPerPage is 10, like in the previous example, end would be 50. data[50] is our last item.

const getCurrentData = () => {
  const start = (currentPage - 1) * itemsPerPage;
  const end = start + itemsPerPage;

  return data.slice(start, end);
};

Here's the final usePagination.jsx

// usePagination.jsx

import { useState } from 'react';
import PropTypes from 'prop-types';

const usePagination = (data, itemsPerPage) => {
  const [currentPage, setCurrentPage] = useState(1);
  const itemCount = data.length;

  const getCurrentData = () => {
    const start = (currentPage - 1) * itemsPerPage;
    const end = start + itemsPerPage;

    return data.slice(start, end);
  };

  const pageCount = Math.ceil(itemCount / itemsPerPage);

  return {
    currentPage, getCurrentData, setCurrentPage, pageCount,
  };
};

usePagination.propTypes = {
  data: PropTypes.arrayOf(PropTypes.any.isRequired).isRequired,
  itemsPerPage: PropTypes.number.isRequired,
};

export default usePagination;

Working Example

Final Thoughts

You should be able to adapt this usePagination hook to other component libraries. I just picked Material-UI, because I haven't ever worked with it before.

I provided a mock data file, but you'll most likely be making a GET request for your data.

You'll have to adjust the propTypes depending on the data you're using.

Have fun and keep coding!

Resources