Skip to content

Instantly share code, notes, and snippets.

@stephengade
Created July 24, 2023 21:00
Show Gist options
  • Save stephengade/db1372466bc6703a8ca53ff75f1e1763 to your computer and use it in GitHub Desktop.
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
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
]
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;
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
];
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