Today we will create server-side paginated table with react-table. In order to see the full code you can visit my github repo.
The technologies used for this project:
- NextJS
- MongoDB
- React-Table
- React-Query
- Mongoose and mongoose-paginated-v2 plugin
And this is the finished product:
Let's have a look at the Table component.
// components/Table.js
import React from 'react';
import { useTable, usePagination } from 'react-table';
function Table({ setPerPage, setPage, columns, data, currentpage, perPage, totalPage }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
pageOptions,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
useControlledState: (state) => {
return React.useMemo(
() => ({
...state,
pageIndex: currentpage,
}),
[state, currentpage]
);
},
initialState: { pageIndex: currentpage }, // Pass our hoisted table state
manualPagination: true,
pageCount: totalPage,
},
usePagination
);
return (
<>
<table {...getTableProps()} className="table-fixed">
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.slice(0, 1).map((column) => (
<th column.getHeaderProps()}>
{column.render('Header')}
</th>
))}
{headerGroup.headers.slice(1).map((column) => (
<th
{...column.getHeaderProps()}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</>
);
}
export default Table;
Here we render the table itself. But where the data is coming from? The parent component uses a custom react-query hook to fetch the data from api routes of NextJS.
// pages/api/schools/index.js
import School from '@models/School';
import dbConnect from '@utils/dbConnect';
dbConnect();
export default async function (req, res) {
switch (req.method) {
case 'GET':
await getSchools(req, res);
break;
default:
res.status(400).json({ success: false });
break;
}
}
const getSchools = async (req, res) => {
try {
let { page, perPage } = req.query;
console.log(page, perPage);
const options = {
page: parseInt(page),
limit: parseInt(perPage),
};
const schools = await School.paginate({}, options);
res.status(200).json({
success: true,
data: schools,
});
} catch (error) {
res.status(400).json({ success: false });
}
};
And custom hook for fetching data from the API Routes:
// utils/useSchools
const { useQuery } = require('react-query');
const axios = require('axios');
export default function useSchools(page, perPage) {
return useQuery(
['schools', page, perPage],
async () => {
const res = await axios.get(`/api/schools?perPage=${perPage}&page=${page}`);
return res.data;
},
{ keepPreviousData: true }
);
}
The last important part is the index.js page where we call the custom hook.
import React, { useState } from 'react';
import Table from '@components/Table/Table';
import useSchools from '@utils/useSchools';
export default function Home() {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const { data: schools, isLoading } = useSchools(page, perPage);
const list = schools?.data.docs.map((i) => {
return {
col1: i.name,
col2: i.il,
col3: i.ilce,
col4: i.kont,
};
});
const data = React.useMemo(() => list, [schools]);
const columns = React.useMemo(
() => [
{
Header: 'okul adi',
accessor: 'col1', // accessor is the "key" in the data
},
{
Header: 'il',
accessor: 'col2',
},
{
Header: 'ilce',
accessor: 'col3',
},
{
Header: 'kontenjan',
accessor: 'col4',
},
],
[]
);
if (isLoading) return <div>loading...</div>;
return (
<div className="p-4 bg-white my-4 rounded shadow-xl grid">
<Table
data={data}
columns={columns}
setPage={setPage}
setPerPage={setPerPage}
currentpage={page}
perPage={perPage}
totalPage={schools?.data.totalPages}
/>
</div>
);
}
This is a bit long code to include all of it here. So you can check the rest of it from my github repo. Cheers!!

Oldest comments (3)
This helped me out soo much! π
I am glad you find it helpful Lorenso π
Thank you! I thing you can make this change to improve the code
Current:
const list = schools?.data.docs.map((i) => {
return {
col1: i.name,
col2: i.il,
col3: i.ilce,
col4: i.kont,
};
});
const data = React.useMemo(() => list, [schools]);
Updated:
const data = React.useMemo(() => {
return schools?.data.docs.map((i) => {
return {
col1: i.name,
col2: i.il,
col3: i.ilce,
col4: i.kont,
};
})
}
, [schools]);