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.
- Convert
App
to use curly braces - Import
usePagination
, - Add the
usePagination
hook with data andITEMS_PER_PAGE
as its arguments, and destructure the returned object. - Update the props in
MyTable
andMyPaginator
with the items we got fromusePagination
.
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!