为 React 实现分页 Material table
Implement pagination for React Material table
我有这个 Spring 用于列出数据库项目的引导端点:
import React, { useEffect, useState } from "react";
import clsx from "clsx";
import {
createStyles,
lighten,
makeStyles,
Theme,
} from "@material-ui/core/styles";
import CircularProgress from "@material-ui/core/CircularProgress";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TablePagination from "@material-ui/core/TablePagination";
import TableRow from "@material-ui/core/TableRow";
import TableSortLabel from "@material-ui/core/TableSortLabel";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Checkbox from "@material-ui/core/Checkbox";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import DeleteIcon from "@material-ui/icons/Delete";
import FilterListIcon from "@material-ui/icons/FilterList";
import axios, { AxiosResponse } from "axios";
import { getTask } from "../../service/merchants";
const baseUrl = "http://185.185.126.15:8080/api";
interface OnboardingTaskDto {
id?: number;
name: string;
}
async function getTask(
page: number,
size: number
): Promise<AxiosResponse<OnboardingTaskDto[]>> {
return await axios.get<OnboardingTaskDto[]>(
`${baseUrl}/management/onboarding/task?page=${page}&size=${size}`
);
}
interface Data {
id: number;
businessName: string;
title: string;
status: string;
}
function createData(
id: number,
businessName: string,
title: string,
status: string
): Data {
return { id, businessName, title, status };
}
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
type Order = "asc" | "desc";
function getComparator<Key extends keyof any>(
order: Order,
orderBy: Key
): (
a: { [key in Key]: number | string },
b: { [key in Key]: number | string }
) => number {
return order === "desc"
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function stableSort<T>(array: T[], comparator: (a: T, b: T) => number) {
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
interface HeadCell {
disablePadding: boolean;
id: keyof Data;
label: string;
numeric: boolean;
}
const headCells: HeadCell[] = [
{ id: "id", numeric: false, disablePadding: true, label: "id" },
{
id: "businessName",
numeric: true,
disablePadding: false,
label: "businessName",
},
{ id: "title", numeric: true, disablePadding: false, label: "title" },
{ id: "status", numeric: true, disablePadding: false, label: "status" },
];
interface EnhancedTableProps {
classes: ReturnType<typeof useStyles>;
numSelected: number;
onRequestSort: (
event: React.MouseEvent<unknown>,
property: keyof Data
) => void;
onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
order: Order;
orderBy: string;
rowCount: number;
}
function EnhancedTableHead(props: EnhancedTableProps) {
const {
classes,
onSelectAllClick,
order,
orderBy,
numSelected,
rowCount,
onRequestSort,
} = props;
const createSortHandler =
(property: keyof Data) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
indeterminate={
numSelected > 0 && numSelected < rowCount
}
checked={rowCount > 0 && numSelected === rowCount}
onChange={onSelectAllClick}
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align={headCell.numeric ? "right" : "left"}
padding={headCell.disablePadding ? "none" : "normal"}
sortDirection={orderBy === headCell.id ? order : false}
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : "asc"}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<span className={classes.visuallyHidden}>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</span>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
}
const useToolbarStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(1),
},
highlight:
theme.palette.type === "light"
? {
color: theme.palette.secondary.main,
backgroundColor: lighten(
theme.palette.secondary.light,
0.85
),
}
: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.secondary.dark,
},
title: {
flex: "1 1 100%",
},
})
);
interface EnhancedTableToolbarProps {
numSelected: number;
onClick: (e: React.MouseEvent<unknown>) => void;
}
const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
const classes = useToolbarStyles();
const { numSelected } = props;
return (
<Toolbar
className={clsx(classes.root, {
[classes.highlight]: numSelected > 0,
})}
>
{numSelected > 0 ? (
<Typography
className={classes.title}
color="inherit"
variant="subtitle1"
component="div"
>
{numSelected} selected
</Typography>
) : (
<Typography
className={classes.title}
variant="h6"
id="tableTitle"
component="div"
>
Customers
</Typography>
)}
{numSelected > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="delete" onClick={props.onClick}>
<DeleteIcon />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton aria-label="filter list">
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</Toolbar>
);
};
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: "100%",
},
paper: {
width: "100%",
marginBottom: theme.spacing(2),
},
table: {
minWidth: 750,
},
visuallyHidden: {
border: 0,
clip: "rect(0 0 0 0)",
height: 1,
margin: -1,
overflow: "hidden",
padding: 0,
position: "absolute",
top: 20,
width: 1,
},
})
);
export default function BusinessCustomersTable() {
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
let updatedState: Data[] = [];
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
useEffect(() => {
const getData = async () => {
setLoading(true);
getTask(1, 100)
.then((resp) => {
console.log(resp.data);
})
.catch((error) => {
console.error(error);
});
const response = await axios.get(apiUrl, {
params: { page: 1, size: 100 },
});
setLoading(false);
const objContent: any = response.data.content;
for (let a = 0; a < objContent.length; a++) {
updatedState[a] = createData(
objContent[a].id,
objContent[a].businessName,
objContent[a].title,
objContent[a].status
);
setRows([...rows, ...updatedState]);
}
};
getData();
}, []);
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter(
(row) => !selected.includes(row.id)
); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className="spinerr">
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
)
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) =>
handleClick(event, row.id)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby":
labelId,
}}
/>
</TableCell>
<TableCell
component="th"
id={labelId}
scope="row"
padding="none"
>
{row.id}
</TableCell>
<TableCell align="right">
{row.businessName}
</TableCell>
<TableCell align="right">
{row.title}
</TableCell>
<TableCell align="right">
{row.status}
</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
沙盒:https://stackblitz.com/edit/react-ts-tnpk85?file=Hello.tsx
当我第一次加载数据并切换页面时,我没有看到对后端的额外请求。看起来数据 table 行数据只加载一次。我需要实现惰性分页并在切换页面时加载当前页面数据。你知道我该如何解决这个问题吗?
有一个简单的方法,就是把useEffect
里面的列表API调用成compoenentDidMount
,把所有的记录都设置到列表数组中。当您调用该页面时,您必须对列表进行切片。
例如 - 您必须每页显示 18 条记录
const [pages,setPages]=useState(0);
const [currentPage,setCurrentPage]=useState(1);
const [isLoading,setIsLoading]=useState(false);
const [items,setItems]=useState([]);
const itemPerPage=18;
const currentPageHandler=page=>{
setCurrentPage(page);
}
useEffect(()=>{
setIsLoading(true);
axios.get('http://185.185.126.15:8080/management/onboarding/task/list')
.then(res=>{
setItems(res.data);
setPageHandler(res.data.length);
setIsLoading(false);
}).catch(error=>{
setIsLoading(false);
console.log(error);
});
},[]);
const setPageHandler =length=>{
const numOfItems=length;
let page = parseInt(numOfItems/itemPerPage);
if(parseInt(numOfItems%18)!==0)
page+=1;
setPages(page);
};
<React.Fragment>
{items.slice((itemPerPage*(currentPage-1)),(itemPerPage*(currentPage)))
.map(item=><Records item={item} key={item.id} />)
}
{ (items.length>=1) && <Pagination pages={pages} currentPageHandler={currentPageHandler}
</React.Fragment>
当您 select 分页组件中的当前页面然后将值作为参数传递给 CurrentPageHandler 方法
注意 - 如果您的平均数据量为 records
<=10000,那么请使用此方法,但如果您必须检索大量数据超过 10000 那么最好的方法就是@dglozano 提到的。
@NafazBenzema 的建议可能会奏效,但您必须记住,根据他的建议,您将仅执行前端分页,这很可能不是您想要的想做。
前端分页意味着:
- 您将触发 仅一个请求 从后端获取所有元素。如果有 10.000.000 条记录,将立即传输所有 10.000.000 条记录(这取决于场景,可能需要您想象的相当长的时间)。
- 之后,当您在 table 中更改页面时将不会触发更多请求,因为您已经获取了所有记录。
- 您将使用前端逻辑(按照 Nafaz 的建议对数组进行切片)选择要在每个页面中显示的记录。
这种方法不一定是错误的,但也不理想,因为它不可扩展。此外,您似乎已经 后端分页了! 所以您应该尝试使用它
为此,您将:
- 只从后端获取第一页记录。例如,只有前 20 条记录...即使您总共有 10.000.000 条记录,也只有前 20 条会被 returned。
- 每个请求不仅要得到页面中的记录,还要得到一个数字,表示记录总数是多少。继续我之前的示例,该数字将是 10.000.000。 table 组件需要知道要请求的页面以及总共有多少页,而无需实际从后端获取所有记录。 它只需要知道总金额。我不确定你的 API 端点当前是否 return 这个总值,但在下面的代码示例中我会假设它确实存在并且它被称为
total
(EDIT : 你的 API 做 return 这个值,它在响应中被称为 totalElements
).
- 然后,每当您单击 table 分页 UI 以转到新页面或更改每页的记录数时,您将触发一个新请求只获取该新页面的元素,然后您必须用响应中收到的行“覆盖”组件状态中的前几行。为此,我们将当前
page
和 rowsPerPage
传递给 useEffect
依赖数组(第二个参数,在您的代码中是一个空数组 []
)。这意味着每当 page
或 rowsPerPage
的值发生变化时,效果中的代码将再次成为 运行。
大致如下所示:
export default function BusinessCustomersTable() {
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
// Your API returns a property called `totalElements`.
// We need to set pass it to the 'count' prop of the TablePagination component to do server side pagination.
// Ref.: https://mui.com/api/table-pagination/
const [totalRows, setTotalRows] = useState(0);
useEffect(() => {
axios.get(apiUrl, {
params: { page, size: rowsPerPage },
})
.then((response) => {
setRows(response.data.content);
// As I said above, your response should return what's the total amount of rows.
// In your API response, that value is called `totalElements`.
setTotalRows(response.data.totalElements);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
}, [page, rowsPerPage]); // Whenever the current 'page' or the amount of 'rowsPerPage' change, your request will be fired again.
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter(
(row) => !selected.includes(row.id)
); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className="spinerr">
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) =>
handleClick(event, row.id)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby":
labelId,
}}
/>
</TableCell>
<TableCell
component="th"
id={labelId}
scope="row"
padding="none"
>
{row.id}
</TableCell>
<TableCell align="right">
{row.businessName}
</TableCell>
<TableCell align="right">
{row.title}
</TableCell>
<TableCell align="right">
{row.status}
</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={totalRows} // This is what your request should be returning in addition to the current page of rows.
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
需要考虑的其他无关事项:
- 如果您在请求完成之前卸载组件(例如,如果您导航到页面中的其他位置),
useEffect
的当前编写方式可能会给您带来一些错误。那是因为它将尝试设置未安装组件的状态(setLoading
和 setRows
)。为了防止这种情况,您可以遵循
- 当您更改
rowsPerPage
时,您似乎也将页面重置为第一个。这将导致效果和请求被触发两次。您可以改为不将页面重置为第一个页面,或者将当前页面的状态和 rowsPerPage 保持在一个公共 useState
挂钩中,并始终同时更新两者。
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
const [totalElements, setData] = useState<any>({});
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
useEffect(() => {
const getData = async (page:number, size: number) => {
let updatedState: Data[] = [];
setLoading(true);
const response = await axios.get(apiUrl, {
params: { page, size },
});
setData(response.data.totalElements);
setLoading(false);
const objContent: any = response.data.content;
for (let a = 0; a < objContent.length; a++) {
updatedState[a] = createData(
objContent[a].id,
objContent[a].businessName,
objContent[a].title,
objContent[a].status
);
}
setRows(updatedState);
};
getData(page, rowsPerPage);
}, [page, rowsPerPage]);
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter((row) => !selected.includes(row.id)); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby='tableTitle'
aria-label='enhanced table'
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className='spinerr'>
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.id)}
role='checkbox'
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding='checkbox'>
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby": labelId,
}}
/>
</TableCell>
<TableCell
component='th'
id={labelId}
scope='row'
padding='none'
>
{row.id}
</TableCell>
<TableCell align='right'>{row.businessName}</TableCell>
<TableCell align='right'>{row.title}</TableCell>
<TableCell align='right'>{row.status}</TableCell>
</TableRow>
);
})}
{/* {emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)} */}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component='div'
count={totalElements}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
我有这个 Spring 用于列出数据库项目的引导端点:
import React, { useEffect, useState } from "react";
import clsx from "clsx";
import {
createStyles,
lighten,
makeStyles,
Theme,
} from "@material-ui/core/styles";
import CircularProgress from "@material-ui/core/CircularProgress";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TablePagination from "@material-ui/core/TablePagination";
import TableRow from "@material-ui/core/TableRow";
import TableSortLabel from "@material-ui/core/TableSortLabel";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Checkbox from "@material-ui/core/Checkbox";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import DeleteIcon from "@material-ui/icons/Delete";
import FilterListIcon from "@material-ui/icons/FilterList";
import axios, { AxiosResponse } from "axios";
import { getTask } from "../../service/merchants";
const baseUrl = "http://185.185.126.15:8080/api";
interface OnboardingTaskDto {
id?: number;
name: string;
}
async function getTask(
page: number,
size: number
): Promise<AxiosResponse<OnboardingTaskDto[]>> {
return await axios.get<OnboardingTaskDto[]>(
`${baseUrl}/management/onboarding/task?page=${page}&size=${size}`
);
}
interface Data {
id: number;
businessName: string;
title: string;
status: string;
}
function createData(
id: number,
businessName: string,
title: string,
status: string
): Data {
return { id, businessName, title, status };
}
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
type Order = "asc" | "desc";
function getComparator<Key extends keyof any>(
order: Order,
orderBy: Key
): (
a: { [key in Key]: number | string },
b: { [key in Key]: number | string }
) => number {
return order === "desc"
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function stableSort<T>(array: T[], comparator: (a: T, b: T) => number) {
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
interface HeadCell {
disablePadding: boolean;
id: keyof Data;
label: string;
numeric: boolean;
}
const headCells: HeadCell[] = [
{ id: "id", numeric: false, disablePadding: true, label: "id" },
{
id: "businessName",
numeric: true,
disablePadding: false,
label: "businessName",
},
{ id: "title", numeric: true, disablePadding: false, label: "title" },
{ id: "status", numeric: true, disablePadding: false, label: "status" },
];
interface EnhancedTableProps {
classes: ReturnType<typeof useStyles>;
numSelected: number;
onRequestSort: (
event: React.MouseEvent<unknown>,
property: keyof Data
) => void;
onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
order: Order;
orderBy: string;
rowCount: number;
}
function EnhancedTableHead(props: EnhancedTableProps) {
const {
classes,
onSelectAllClick,
order,
orderBy,
numSelected,
rowCount,
onRequestSort,
} = props;
const createSortHandler =
(property: keyof Data) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
indeterminate={
numSelected > 0 && numSelected < rowCount
}
checked={rowCount > 0 && numSelected === rowCount}
onChange={onSelectAllClick}
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align={headCell.numeric ? "right" : "left"}
padding={headCell.disablePadding ? "none" : "normal"}
sortDirection={orderBy === headCell.id ? order : false}
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : "asc"}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<span className={classes.visuallyHidden}>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</span>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
}
const useToolbarStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(1),
},
highlight:
theme.palette.type === "light"
? {
color: theme.palette.secondary.main,
backgroundColor: lighten(
theme.palette.secondary.light,
0.85
),
}
: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.secondary.dark,
},
title: {
flex: "1 1 100%",
},
})
);
interface EnhancedTableToolbarProps {
numSelected: number;
onClick: (e: React.MouseEvent<unknown>) => void;
}
const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
const classes = useToolbarStyles();
const { numSelected } = props;
return (
<Toolbar
className={clsx(classes.root, {
[classes.highlight]: numSelected > 0,
})}
>
{numSelected > 0 ? (
<Typography
className={classes.title}
color="inherit"
variant="subtitle1"
component="div"
>
{numSelected} selected
</Typography>
) : (
<Typography
className={classes.title}
variant="h6"
id="tableTitle"
component="div"
>
Customers
</Typography>
)}
{numSelected > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="delete" onClick={props.onClick}>
<DeleteIcon />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton aria-label="filter list">
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</Toolbar>
);
};
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: "100%",
},
paper: {
width: "100%",
marginBottom: theme.spacing(2),
},
table: {
minWidth: 750,
},
visuallyHidden: {
border: 0,
clip: "rect(0 0 0 0)",
height: 1,
margin: -1,
overflow: "hidden",
padding: 0,
position: "absolute",
top: 20,
width: 1,
},
})
);
export default function BusinessCustomersTable() {
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
let updatedState: Data[] = [];
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
useEffect(() => {
const getData = async () => {
setLoading(true);
getTask(1, 100)
.then((resp) => {
console.log(resp.data);
})
.catch((error) => {
console.error(error);
});
const response = await axios.get(apiUrl, {
params: { page: 1, size: 100 },
});
setLoading(false);
const objContent: any = response.data.content;
for (let a = 0; a < objContent.length; a++) {
updatedState[a] = createData(
objContent[a].id,
objContent[a].businessName,
objContent[a].title,
objContent[a].status
);
setRows([...rows, ...updatedState]);
}
};
getData();
}, []);
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter(
(row) => !selected.includes(row.id)
); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className="spinerr">
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
)
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) =>
handleClick(event, row.id)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby":
labelId,
}}
/>
</TableCell>
<TableCell
component="th"
id={labelId}
scope="row"
padding="none"
>
{row.id}
</TableCell>
<TableCell align="right">
{row.businessName}
</TableCell>
<TableCell align="right">
{row.title}
</TableCell>
<TableCell align="right">
{row.status}
</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
沙盒:https://stackblitz.com/edit/react-ts-tnpk85?file=Hello.tsx
当我第一次加载数据并切换页面时,我没有看到对后端的额外请求。看起来数据 table 行数据只加载一次。我需要实现惰性分页并在切换页面时加载当前页面数据。你知道我该如何解决这个问题吗?
有一个简单的方法,就是把useEffect
里面的列表API调用成compoenentDidMount
,把所有的记录都设置到列表数组中。当您调用该页面时,您必须对列表进行切片。
例如 - 您必须每页显示 18 条记录
const [pages,setPages]=useState(0);
const [currentPage,setCurrentPage]=useState(1);
const [isLoading,setIsLoading]=useState(false);
const [items,setItems]=useState([]);
const itemPerPage=18;
const currentPageHandler=page=>{
setCurrentPage(page);
}
useEffect(()=>{
setIsLoading(true);
axios.get('http://185.185.126.15:8080/management/onboarding/task/list')
.then(res=>{
setItems(res.data);
setPageHandler(res.data.length);
setIsLoading(false);
}).catch(error=>{
setIsLoading(false);
console.log(error);
});
},[]);
const setPageHandler =length=>{
const numOfItems=length;
let page = parseInt(numOfItems/itemPerPage);
if(parseInt(numOfItems%18)!==0)
page+=1;
setPages(page);
};
<React.Fragment>
{items.slice((itemPerPage*(currentPage-1)),(itemPerPage*(currentPage)))
.map(item=><Records item={item} key={item.id} />)
}
{ (items.length>=1) && <Pagination pages={pages} currentPageHandler={currentPageHandler}
</React.Fragment>
当您 select 分页组件中的当前页面然后将值作为参数传递给 CurrentPageHandler 方法
注意 - 如果您的平均数据量为 records
<=10000,那么请使用此方法,但如果您必须检索大量数据超过 10000 那么最好的方法就是@dglozano 提到的。
@NafazBenzema 的建议可能会奏效,但您必须记住,根据他的建议,您将仅执行前端分页,这很可能不是您想要的想做。
前端分页意味着:
- 您将触发 仅一个请求 从后端获取所有元素。如果有 10.000.000 条记录,将立即传输所有 10.000.000 条记录(这取决于场景,可能需要您想象的相当长的时间)。
- 之后,当您在 table 中更改页面时将不会触发更多请求,因为您已经获取了所有记录。
- 您将使用前端逻辑(按照 Nafaz 的建议对数组进行切片)选择要在每个页面中显示的记录。
这种方法不一定是错误的,但也不理想,因为它不可扩展。此外,您似乎已经 后端分页了! 所以您应该尝试使用它
为此,您将:
- 只从后端获取第一页记录。例如,只有前 20 条记录...即使您总共有 10.000.000 条记录,也只有前 20 条会被 returned。
- 每个请求不仅要得到页面中的记录,还要得到一个数字,表示记录总数是多少。继续我之前的示例,该数字将是 10.000.000。 table 组件需要知道要请求的页面以及总共有多少页,而无需实际从后端获取所有记录。 它只需要知道总金额。我不确定你的 API 端点当前是否 return 这个总值,但在下面的代码示例中我会假设它确实存在并且它被称为
total
(EDIT : 你的 API 做 return 这个值,它在响应中被称为totalElements
). - 然后,每当您单击 table 分页 UI 以转到新页面或更改每页的记录数时,您将触发一个新请求只获取该新页面的元素,然后您必须用响应中收到的行“覆盖”组件状态中的前几行。为此,我们将当前
page
和rowsPerPage
传递给useEffect
依赖数组(第二个参数,在您的代码中是一个空数组[]
)。这意味着每当page
或rowsPerPage
的值发生变化时,效果中的代码将再次成为 运行。
大致如下所示:
export default function BusinessCustomersTable() {
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
// Your API returns a property called `totalElements`.
// We need to set pass it to the 'count' prop of the TablePagination component to do server side pagination.
// Ref.: https://mui.com/api/table-pagination/
const [totalRows, setTotalRows] = useState(0);
useEffect(() => {
axios.get(apiUrl, {
params: { page, size: rowsPerPage },
})
.then((response) => {
setRows(response.data.content);
// As I said above, your response should return what's the total amount of rows.
// In your API response, that value is called `totalElements`.
setTotalRows(response.data.totalElements);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
}, [page, rowsPerPage]); // Whenever the current 'page' or the amount of 'rowsPerPage' change, your request will be fired again.
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter(
(row) => !selected.includes(row.id)
); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className="spinerr">
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) =>
handleClick(event, row.id)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby":
labelId,
}}
/>
</TableCell>
<TableCell
component="th"
id={labelId}
scope="row"
padding="none"
>
{row.id}
</TableCell>
<TableCell align="right">
{row.businessName}
</TableCell>
<TableCell align="right">
{row.title}
</TableCell>
<TableCell align="right">
{row.status}
</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={totalRows} // This is what your request should be returning in addition to the current page of rows.
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
需要考虑的其他无关事项:
- 如果您在请求完成之前卸载组件(例如,如果您导航到页面中的其他位置),
useEffect
的当前编写方式可能会给您带来一些错误。那是因为它将尝试设置未安装组件的状态(setLoading
和setRows
)。为了防止这种情况,您可以遵循 - 当您更改
rowsPerPage
时,您似乎也将页面重置为第一个。这将导致效果和请求被触发两次。您可以改为不将页面重置为第一个页面,或者将当前页面的状态和 rowsPerPage 保持在一个公共useState
挂钩中,并始终同时更新两者。
const classes = useStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<keyof Data>("businessName");
const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [rows, setRows] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
const [totalElements, setData] = useState<any>({});
// TODO - move this to API file
const apiUrl = "http://185.185.126.15:8080/api/management/onboarding/task";
useEffect(() => {
const getData = async (page:number, size: number) => {
let updatedState: Data[] = [];
setLoading(true);
const response = await axios.get(apiUrl, {
params: { page, size },
});
setData(response.data.totalElements);
setLoading(false);
const objContent: any = response.data.content;
for (let a = 0; a < objContent.length; a++) {
updatedState[a] = createData(
objContent[a].id,
objContent[a].businessName,
objContent[a].title,
objContent[a].status
);
}
setRows(updatedState);
};
getData(page, rowsPerPage);
}, [page, rowsPerPage]);
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleDeleteClick = async () => {
// npm install qs
var qs = require("qs");
const response = await axios.delete(apiUrl, {
params: {
ids: selected,
},
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
if (response.status === 204) {
const updatedData = rows.filter((row) => !selected.includes(row.id)); // It'll return all data except selected ones
setRows(updatedData); // reset rows to display in table.
}
};
const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar
numSelected={selected.length}
onClick={handleDeleteClick}
/>
<TableContainer>
<Table
className={classes.table}
aria-labelledby='tableTitle'
aria-label='enhanced table'
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{loading ? (
<div className='spinerr'>
<CircularProgress />
</div>
) : null}
{stableSort(rows, getComparator(order, orderBy))
.map((row, index) => {
const isItemSelected = isSelected(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.id)}
role='checkbox'
aria-checked={isItemSelected}
tabIndex={-1}
key={row.businessName}
selected={isItemSelected}
>
<TableCell padding='checkbox'>
<Checkbox
checked={isItemSelected}
inputProps={{
"aria-labelledby": labelId,
}}
/>
</TableCell>
<TableCell
component='th'
id={labelId}
scope='row'
padding='none'
>
{row.id}
</TableCell>
<TableCell align='right'>{row.businessName}</TableCell>
<TableCell align='right'>{row.title}</TableCell>
<TableCell align='right'>{row.status}</TableCell>
</TableRow>
);
})}
{/* {emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)} */}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component='div'
count={totalElements}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}