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!!
Top comments (3)
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]);
This helped me out soo much! 🙏
I am glad you find it helpful Lorenso 👍