19
loading...
This website collects cookies to deliver better user experience
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'
columns.jsx
import { USERS_COLUMNS } from "./columns"
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"
},
]
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"
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.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.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.const queryClient = new QueryClient()
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 within this client, I have just added it in my this one page only.... 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;
...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 }) => {
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) => {
let paramStr = ''
if( pageFilter.trim().length > 1 ) {
paramStr = `&keyword=${pageFilter}`
}
if( pageSortBy.length > 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}`)
}
}
DataTable
componentconst 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' && <p>No results found</p>
}
{data?.count &&
<>
<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) && (
<>
<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>
</>
)}
</>
)
}
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>
);
manualPagination: true,
pageCount: data ? totalPageCount : null,
autoResetSortBy: false,
autoResetExpanded: false,
autoResetPage: false