【问题标题】:Implement pagination for React Material table为 React Material 表实现分页
【发布时间】:2021-09-07 14:21:45
【问题描述】:

我有这个 Spring Boot 端点用于列出数据库中的项目:

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

当第一次加载数据并且我切换页面时,我看不到对后端的其他请求。看起来数据表行数据只加载一次。我需要在切换页面时实现延迟分页并加载当前页面数据。你知道我该如何解决这个问题吗?

【问题讨论】:

    标签: reactjs typescript spring-boot material-ui react-typescript


    【解决方案1】:

    @NafazBenzema 的建议可能会奏效,但您必须记住,根据他的建议,您将仅进行前端分页,这很可能不是您想要做的。

    前端分页意味着:

    1. 您将触发一个请求以从后端获取所有元素。如果有 10.000.000 条记录,则所有 10.000.000 条记录将一次全部传输(这取决于场景,可能需要您想象的相当长的时间)。
    2. 之后,当您更改表中的页面时,不会再触发任何请求,因为您已经获取了所有记录。
    3. 您将使用前端中的逻辑选择在每个页面中显示哪些记录(按照 Nafaz 的建议对数组进行切片)。

    这种方法不一定是错误的,但也不理想,因为它不可扩展。此外,您似乎已经有后端分页!所以您应该尝试使用它?

    为此,您将:

    1. 仅从后端获取记录的第一页。例如,只有前 20 条记录...即使您总共有 10.000.000 条记录,也只会返回前 20 条。
    2. 每个请求不仅应该获取页面中的记录,还应该获取一个表示记录总数的数字。继续我之前的示例,该数字将是 10.000.000。表格组件需要知道要请求哪个页面以及总共有多少页面,而无需实际从后端获取所有记录。 它只需要知道总量。我不确定您的 API 端点当前是否返回此总值,但在下面的代码示例中,我会假设它确实如此,并且它被称为 total (编辑:您的 API 确实返回这个值,它被称为 @ 987654324@ 在回复中)
    3. 然后,每当您单击表格分页 UI 以转到新页面或更改每页的记录数时,您将触发新请求以仅获取该新页面的元素,然后您必须用响应中收到的行“覆盖”组件状态中的先前行。为此,我们将当前的pagerowsPerPage 传递给useEffect 依赖数组(第二个参数,在您的代码中是一个空数组[])。这意味着每当pagerowsPerPage的值发生变化时,效果内的代码都会再次运行。

    这或多或少应该是这样的:

    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 的方式可能会给您一些错误。那是因为它将尝试设置未安装组件的状态(setLoadingsetRows)。为了防止这种情况,您可以关注this other answer.
    • 当您更改rowsPerPage 时,您似乎也在将页面重置为第一个。这将导致效果和请求被触发两次。您可以改为不将页面重置为第一个页面,或者将当前页面的状态和 rowsPerPage 保持在一个常见的 useState 挂钩中,并始终同时更新两者。

    【讨论】:

    • 我在尝试获取内容时出错。这是我发出请求时服务器的响应:pastebin.com/WmpRYDAv 看起来你需要稍微调整一下代码。
    • @PeterPenzov 我已经更新了代码,以便为您的 API 响应 (totalElements) 中的记录总数使用正确的值。除此之外,不确定错误可能是什么,因此您必须更具体。
    • 对于这一行 const mappedDataRows = response.data.content.map(dataObject =&gt; createData( 我得到 2 个错误:TS2339: Property 'content' does not exist on type 'OnboardingTaskDto[]'.TS7006: Parameter 'dataObject' implicitly has an 'any' type. 你能建议吗?
    • @PeterPenzov 似乎您在createDatagetTasks 函数中的某些Typescripts 类型定义不正确。我再次更新了代码,显示了一个没有这两个有问题的功能的版本。无论如何,这不是与分页有关的问题。
    • 到目前为止问题已经消失,但我看到了另一个问题:第一页数据已加载。但是下一页是空的,而且只是添加空白空间。是否可以在本地测试以重现问题?
    【解决方案2】:
      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>
      );
    }
    

    【讨论】:

      【解决方案3】:

      有一个简单的方法,你必须以compoenentDidMount 调用useEffect 中的列表API,并将所有记录设置到列表数组中。当您调用页面时,您必须对列表进行切片。

      例如 - 您必须每页显示 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>
      

      当您在分页组件中选择当前页面时,然后将值作为参数传递给 CurrentPageHandler 方法

      注意 - 如果您有像 records

      【讨论】:

      • 谢谢。你能把这段代码实现到我的代码中,以便我测试它吗?
      猜你喜欢
      • 2021-01-27
      • 1970-01-01
      • 2018-08-03
      • 2017-12-29
      • 2020-05-01
      • 1970-01-01
      • 2017-03-07
      • 2020-11-18
      • 2020-02-06
      相关资源
      最近更新 更多