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.