Created
July 24, 2023 21:00
-
-
Save stephengade/db1372466bc6703a8ca53ff75f1e1763 to your computer and use it in GitHub Desktop.
How to Build a Powerful Table in ReactJS with Tanstack and Material UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const DummyData = [ | |
{ | |
_id: "0x11", | |
name: "Stephen", | |
age: 30, | |
job: "Frontend Engineer", | |
country: "Nigeria", | |
status: "active" | |
}, | |
{ | |
_id: "0x12", | |
name: "Gbolagade", | |
age: 60, | |
job: "Technical writer", | |
country: "Nigeria", | |
status: "not active" | |
} | |
// you can add more | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from "react"; | |
import { Box } from "@mui/material"; | |
// Import these components correctly depending on your structure | |
import TableUI from "@/components/TableUI/TableUI"; | |
import { Columns } from "./Column"; | |
import {DummyData} from "./dummydata" | |
const StaffTable = () => { | |
// Initiate your states | |
const [items, setItems] = useState([]); | |
const [loading, setLoading] = useState(false); | |
const [currentPage, setCurrentPage] = useState(1); | |
const [totalPageCount, setTotalPageCount] = useState(1); | |
// For pagination, define maximum of data per page | |
const ITEMS_PER_PAGE = 10; | |
// useEffect to get the data | |
useEffect(() => { | |
const fetchData = async () => { | |
setLoading(true); | |
try { | |
// If you have an API, do your normal axios or fetch | |
const response = await DummyData | |
setItems(response); | |
setTotalPageCount(ITEMS_PER_PAGE) | |
setLoading(false); | |
} catch (error) { | |
console.error("Error fetching data:", error); | |
setLoading(false); | |
} | |
}; | |
fetchData(); | |
}, [currentPage]); | |
const handlePageChange = (page: any) => { | |
setCurrentPage(page); | |
}; | |
// handle the search here | |
const handleSearch = (query: string) => { | |
if (query.trim() === "") { | |
// Restore the original data when the search query is empty | |
setItems(items); | |
} else { | |
const filteredData = items.filter((item: any) => | |
item.name.toLowerCase().includes(query.toLowerCase()) || | |
item.age.includes(query) || | |
item.job.includes(query) | |
); | |
setItems(filteredData || []); | |
} | |
}; | |
const DataLength = items.length; | |
return ( | |
<section className="mt-5"> | |
<h3 className="text-[18px] mb-2 md:text-[24px] text-black"> | |
</h3> | |
<Box> | |
<TableUI | |
data={items} | |
columns={Columns} | |
searchLabel="Search by Name or job title" | |
EmptyText="No staff found!" | |
isFetching={loading} | |
pageCount={totalPageCount} | |
page={handlePageChange} | |
search={handleSearch} | |
/> | |
</Box> | |
</section> | |
); | |
}; | |
export default StaffTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Chip } from "@mui/material"; | |
import { ColumnDef } from "@tanstack/react-table"; | |
export const Columns: ColumnDef<any, any>[] = [ | |
{ | |
accessorKey: "Name", | |
header: "Name", | |
cell: (user: any) => { | |
return <span>{user.row.original.name}</span> | |
}, | |
}, | |
{ | |
accessorKey: "Age", | |
header: "Age", | |
cell: (user: any) => { | |
return <span>{user.row.original.age}</span> | |
}, | |
}, | |
{ | |
accessorKey: "Job", | |
header: "Job", | |
cell: (user: any) => { | |
return <span>{user.row.original.job}</span> | |
}, | |
}, | |
// You can follow the structure to display other data such as country and status | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Box, | |
Paper, | |
Skeleton, | |
Table as MuiTable, | |
TableHead, | |
TableRow, | |
TableCell, | |
TableBody, | |
TextField, | |
Menu, | |
MenuItem, | |
Pagination, styled, TableRow | |
} from "@mui/material"; | |
import { | |
Cell, | |
ColumnDef, | |
flexRender, | |
getCoreRowModel, | |
Row, | |
useReactTable, | |
} from "@tanstack/react-table"; | |
import { debounce } from "lodash"; | |
import { ChangeEvent, FC, memo, ReactElement, useMemo, useState } from "react"; | |
// Styles with styled-component | |
export const StyledTableRow = styled(TableRow)` | |
&:nth-of-type(odd) { | |
background-color: #f1f1f1; | |
} | |
&:last-child td, | |
&:last-child th { | |
border: 0; | |
} | |
:hover { | |
background-color: #d9d9d9; | |
} | |
`; | |
export const StyledPagination = styled(Pagination)` | |
display: flex; | |
justify-content: center; | |
margin-top: 1rem; | |
`; | |
// Typescript interface | |
interface TableProps { | |
data: any[]; | |
columns: ColumnDef<any>[]; | |
isFetching?: boolean; | |
skeletonCount?: number; | |
skeletonHeight?: number; | |
headerComponent?: JSX.Element; | |
pageCount?: number; | |
page?: (page: number) => void; | |
search?: (search: string) => void; | |
onClickRow?: (cell: Cell<any, unknown>, row: Row<any>) => void; | |
searchLabel?: string; | |
EmptyText?: string; | |
children?: React.ReactNode | React.ReactElement | |
handleRow?: () => void | |
} | |
// The main table | |
const TableUI: FC<TableProps> = ({ | |
data, | |
columns, | |
isFetching, | |
skeletonCount = 10, | |
skeletonHeight = 28, | |
headerComponent, | |
pageCount, | |
search, | |
onClickRow, | |
page, | |
searchLabel = "Search", | |
EmptyText, | |
children, | |
handleRow | |
}) => { | |
const [paginationPage, setPaginationPage] = useState(1); | |
const memoizedData = useMemo(() => data, [data]); | |
const memoizedColumns = useMemo(() => columns, [columns]); | |
const memoisedHeaderComponent = useMemo( | |
() => headerComponent, | |
[headerComponent] | |
); | |
const { getHeaderGroups, getRowModel, getAllColumns } = useReactTable({ | |
data: memoizedData, | |
columns: memoizedColumns, | |
getCoreRowModel: getCoreRowModel(), | |
manualPagination: true, | |
pageCount, | |
}); | |
const skeletons = Array.from({ length: skeletonCount }, (x, i) => i); | |
const columnCount = getAllColumns().length; | |
const noDataFound = | |
!isFetching && (!memoizedData || memoizedData.length === 0); | |
const handleSearchChange = ( | |
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | |
) => { | |
search && search(e.target.value); | |
}; | |
const handlePageChange = ( | |
event: ChangeEvent<unknown>, | |
currentPage: number | |
) => { | |
setPaginationPage(currentPage === 0 ? 1 : currentPage); | |
page?.(currentPage === 0 ? 1 : currentPage); | |
}; | |
return ( | |
<Paper elevation={2} style={{ padding: "0 0 1rem 0" }}> | |
<Box paddingX="1rem"> | |
{memoisedHeaderComponent && <Box>{memoisedHeaderComponent}</Box>} | |
{search && ( | |
<TextField | |
onChange={debounce(handleSearchChange, 1000)} | |
size="small" | |
label={searchLabel} | |
margin="normal" | |
variant="outlined" | |
fullWidth | |
/> | |
)} | |
</Box> | |
<Box style={{ overflowX: "auto" }}> | |
<MuiTable> | |
{!isFetching && ( | |
<TableHead> | |
{getHeaderGroups().map((headerGroup) => ( | |
<TableRow key={headerGroup.id} className="bg-[#000]"> | |
{headerGroup.headers.map((header) => ( | |
<TableCell key={header.id} className="text-white text-sm font-cambon" > | |
{header.isPlaceholder | |
? null | |
: flexRender( | |
header.column.columnDef.header, | |
header.getContext() | |
)} | |
</TableCell> | |
))} | |
</TableRow> | |
))} | |
</TableHead> | |
)} | |
<TableBody> | |
{!isFetching ? ( | |
getRowModel()?.rows.map((row) => ( | |
<StyledTableRow key={row.id} onClick={handleRow}> | |
{row.getVisibleCells().map((cell) => ( | |
<TableCell | |
onClick={() => onClickRow?.(cell, row)} | |
key={cell.id} | |
className="text-[#2E353A] text-base font-graphik" | |
> | |
{flexRender( | |
cell.column.columnDef.cell, | |
cell.getContext() | |
)} | |
</TableCell> | |
))} | |
</StyledTableRow> | |
) | |
) | |
) : ( | |
<> | |
{skeletons.map((skeleton) => ( | |
<TableRow key={skeleton}> | |
{Array.from({ length: columnCount }, (x, i) => i).map( | |
(elm) => ( | |
<TableCell key={elm}> | |
<Skeleton height={skeletonHeight} /> | |
</TableCell> | |
) | |
)} | |
</TableRow> | |
))} | |
</> | |
)} | |
</TableBody> | |
</MuiTable> | |
</Box> | |
{noDataFound && ( | |
<Box my={2} textAlign="center"> | |
{EmptyText} | |
</Box> | |
)} | |
{pageCount && page && ( | |
<StyledPagination | |
count={pageCount} | |
page={paginationPage} | |
onChange={handlePageChange} | |
color="primary" | |
showFirstButton | |
showLastButton | |
/> | |
)} | |
</Paper> | |
); | |
}; | |
Table.defaultProps = { | |
EmptyText: "No Data is found" | |
} | |
export default memo(TableUI); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment