After following [a post](https://dev.to/elangobharathi/server-side-pagination-using-react-table-v7-and-react-query-v3-3lck) on dev.to I was able to setup a basic table with React Table Server Side Pagination. However since the post did not have the sorting and search features I had to extend it and hence this one!
– You may find full code here at my github repo
Here is what my final table with React Table Server Side Pagination looks like:

Let’s start with some initial imports. My example uses `react-query` so make sure you have it installed. It is a great library anyway. I also use `axios` library for making ajax calls.
import React, {useState, useEffect, useMemo} from "react"
import { useTable, usePagination, useSortBy } from "react-table"
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
import axios from 'axios'Next I import user columns which I place in another file named `columns.jsx`
import { USERS_COLUMNS } from "./columns"And here are the contents of `columns.jsx` file:
export const USERS_COLUMNS = [
{ Header: "Email", accessor: "email", },
{ Header: "Name", accessor: "name", },
{ Header: "Phone", accessor: "phone", },
{ Header: "Role", accessor: "role", },
{ Header: "Employee Number", accessor: "employee_number" },
]Next imports are:
import SortIcon from 'mdi-react/SortIcon' import SortAscendingIcon from 'mdi-react/SortAscendingIcon' import SortDescendingIcon from 'mdi-react/SortDescendingIcon' import ReactTablePagination from '@/shared/components/table/components/ReactTablePagination' import UsersFilter from "./UsersFilter"
Let me explain it a bit. First three imports are icons used for sorting. Next to it is `ReactTablePagination` component I have created for pagination links and last one `UsersFilter` is the search area where I place search box with a submit link. I may also want to add more filters later on.
I will post `ReactTablePagination` and `UsersFilter` code down the page. Let’s first work with our current `UsersIndex.jsx` file and its main component `DataTable` but before that let me post some declarations I have made outside of `DataTable` component.
Okay, once all the imports are done at the top of this page. Let start with structure of rest of this file.
Since I am using `react-query`, and you should also consider using it if your app is doing ajax requests for data extensively, I will wrap my DataTable component within `QueryClientProvider` which is exported from `react-query` library if you noticed it at the top of the page.
So after imports I initialise the queryClient
const queryClient = new QueryClient()
… and wrap my `DataTable` with `QueryClientProvider` by passing client to it and export it at the end of the page. You may also consider to wrap you main <App> within this client, I have just added it in my this one page only.
This is the overall structure of UsersIndex.jsx file
... imports at the top of the file
const queryClient = new QueryClient()
... other file code const DataTable = () => { ... component code } const TableWrapper = () => { return ( <QueryClientProvider client={queryClient}> <DataTable /> </QueryClientProvider> ) } export default TableWrapper;Lets dive into the `…other file code first`. This is the code which is before the main `DataTable` component.
const initialState = {
queryPageIndex: 0,
queryPageSize: 10,
totalCount: 0,
queryPageFilter: "",
queryPageSortBy: [],
};
const PAGE_CHANGED = 'PAGE_CHANGED'
const PAGE_SIZE_CHANGED = 'PAGE_SIZE_CHANGED'
const PAGE_SORT_CHANGED = 'PAGE_SORT_CHANGED'
const PAGE_FILTER_CHANGED = 'PAGE_FILTER_CHANGED'
const TOTAL_COUNT_CHANGED = 'TOTAL_COUNT_CHANGED'
const reducer = (state, { type, payload }) =& gt; {
switch (type) {
case PAGE_CHANGED:
return {
...state,
queryPageIndex: payload,
};
case PAGE_SIZE_CHANGED:
return {
...state,
queryPageSize: payload,
};
case PAGE_SORT_CHANGED:
return {
...state,
queryPageSortBy: payload,
};
case PAGE_FILTER_CHANGED:
return {
...state,
queryPageFilter: payload,
};
case TOTAL_COUNT_CHANGED:
return {
...state,
totalCount: payload,
};
default:
throw new Error(`Unhandled action type: ${type}`)
}
};
const fetchUsersData = async(page, pageSize, pageFilter, pageSortBy) =& gt; {
let paramStr = ''
if (pageFilter.trim().length & gt; 1 ) {
paramStr = `&keyword=${pageFilter}`
}
if (pageSortBy.length & gt; 0 ) {
const sortParams = pageSortBy[0];
const sortyByDir = sortParams.desc ? 'desc' : 'asc'
paramStr = `${paramStr}&sortby=${sortParams.id}&direction=${sortyByDir}`
}
try {
const response = await axios.get(
`/users?page=${page + 1}&limit=${pageSize}${paramStr}`
);
const results = response.data.data;
const data = {
results: results,
count: response.data.total
};
return data;
} catch (e) {
throw new Error(`API error:${e?.message}`)
}
}New thing to notice in the code above is the use of reducer. If you are not sure how reducers work you should check [this post](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers) or a simplified [post here](https://www.robinwieruch.de/javascript-reducer/)
Also there is _fetchUsersData_ function which is responsible for fetching user data and most of it is self-explaining.
React Table Server Side Pagination Component
And finally here is the React Table Server Side Pagination `DataTable` component
const DataTable = () => {
const [keyword, setKeyword] = useState('');
const [useFilter, setUseFilter] = useState(false);
const onClickFilterCallback = (filter) => {
if (filter.trim() === "") {
alert('Please enter a keyword to search!')
return
}
if (filter === keyword) {
alert('No change in search')
return
}
setUseFilter(true)
setKeyword(filter)
}
let columns = useMemo(() => USERS_COLUMNS, [])
const [{ queryPageIndex, queryPageSize, totalCount, queryPageFilter, queryPageSortBy }, dispatch] =
useReducer(reducer, initialState);
const { isLoading, error, data, isSuccess } = useQuery(
['users', queryPageIndex, queryPageSize, queryPageFilter, queryPageSortBy],
() => fetchUsersData(queryPageIndex, queryPageSize, queryPageFilter, queryPageSortBy),
{
keepPreviousData: false,
staleTime: Infinity,
}
);
const totalPageCount = Math.ceil(totalCount / queryPageSize)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
page,
pageCount,
pageOptions,
gotoPage,
previousPage,
canPreviousPage,
nextPage,
canNextPage,
setPageSize,
state: { pageIndex, pageSize, sortBy }
} = useTable({
columns,
data: data?.results || [],
initialState: {
pageIndex: queryPageIndex,
pageSize: queryPageSize,
sortBy: queryPageSortBy,
},
manualPagination: true,
pageCount: data ? totalPageCount : null,
autoResetSortBy: false,
autoResetExpanded: false,
autoResetPage: false
},
useSortBy,
usePagination,
);
const manualPageSize = []
useEffect(() => {
dispatch({ type: PAGE_CHANGED, payload: pageIndex });
}, [pageIndex]);
useEffect(() => {
dispatch({ type: PAGE_SIZE_CHANGED, payload: pageSize });
gotoPage(0);
}, [pageSize, gotoPage]);
useEffect(() => {
dispatch({ type: PAGE_SORT_CHANGED, payload: sortBy });
gotoPage(0);
}, [sortBy, gotoPage]);
useEffect(() => {
if (useFilter) {
dispatch({ type: PAGE_FILTER_CHANGED, payload: keyword });
gotoPage(0);
}
}, [keyword, gotoPage, useFilter]);
useEffect(() => {
if (data?.count) {
dispatch({
type: TOTAL_COUNT_CHANGED,
payload: data.count,
});
}
}, [data?.count]);
if (error) {
return < p > Error </p>;
}
if (isLoading) {
return < p >Loading...</p>;
}
if (isSuccess)
return (
<>
<div className='table react-table' >
<form className="form form--horizontal" >
<div className="form__form-group" >
<div className="col-md-9 col-lg-9" >
<UsersFilter onClickFilterCallback={onClickFilterCallback} defaultKeyword={keyword} />
</div>
<div className="col-md-3 col-lg-3 text-right pr-0" >
<Link style={{ maxWidth: '200px' }
}
className="btn btn-primary account__btn account__btn--small"
to="/users/add"
>Add new user
</Link>
</div>
</div>
</form>
{
typeof data?.count === 'undefined' & amp;& amp; < p >No results found </p>
}
{
data?.count & amp;& amp;
<>
<table {...getTableProps()} className="table" >
< thead >
{
headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{
headerGroup.headers.map(column => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
{column.isSorted ? <Sorting column={column} /> : ''}
</th>
))
}
</tr>
))
}
</thead>
<tbody className="table table--bordered" {...getTableBodyProps()}>
{
page.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{
row.cells.map(cell => {
return <td {...cell.getCellProps()}>< span > {cell.render('Cell')}</span></td >
})
}
</tr>
)
})
}
</tbody>
</table>
</>
}
</div>
{
(rows.length > 0) & amp;& amp; (
<>
< ReactTablePagination
page={page}
gotoPage={gotoPage}
previousPage={previousPage}
nextPage={nextPage}
canPreviousPage={canPreviousPage}
canNextPage={canNextPage}
pageOptions={pageOptions}
pageSize={pageSize}
pageIndex={pageIndex}
pageCount={pageCount}
setPageSize={setPageSize}
manualPageSize={manualPageSize}
dataLength={totalCount}
/>
<div className="pagination justify-content-end mt-2" >
< span >
Go to page: {' '}
< input
type="number"
value={pageIndex + 1
}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: '100px' }}
/>
</span>{' '}
< select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{
[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize} >
Show {pageSize}
</option>
))
}
</select>
</div>
</>
)}
</>
)
}And there is one helper component which is outside of `DataTable` component. I just placed it at the bottom, just before the `TableWrapper`.
const Sorting = ({ column }) => (
<span className="react-table__column-header sortable">
{column.isSortedDesc === undefined ? (
<SortIcon />
) : (
<span>
{column.isSortedDesc
? <SortAscendingIcon />
: <SortDescendingIcon />}
</span>
)}
</span>
);It is not possible to explain every line and I hope the code makes sense to you. There is one thing I want to mention though. Notice the last three settings in the block:
manualPagination: true, pageCount: data ? totalPageCount : null, autoResetSortBy: false, autoResetExpanded: false, autoResetPage: false
I had to set them in order to get rid of “Maximum update depth exceeded” error after I turned manualPagination on and implemented server side pagination with sorting and search in my reactjs application. ([See ref here](https://github.com/tannerlinsley/react-table/issues/2369))
– Full code here at my github repo.