title: Implement pagination for React Material table
body: <p>I have this Spring Boot endpoint for listing items from database:</p>
  <pre><code>import React, { useEffect, useState } from &quot;react&quot;;
  
  import clsx from &quot;clsx&quot;;
  import {
    createStyles,
    lighten,
    makeStyles,
    Theme,
  } from &quot;@material-ui/core/styles&quot;;
  import CircularProgress from &quot;@material-ui/core/CircularProgress&quot;;
  import Table from &quot;@material-ui/core/Table&quot;;
  import TableBody from &quot;@material-ui/core/TableBody&quot;;
  import TableCell from &quot;@material-ui/core/TableCell&quot;;
  import TableContainer from &quot;@material-ui/core/TableContainer&quot;;
  import TableHead from &quot;@material-ui/core/TableHead&quot;;
  import TablePagination from &quot;@material-ui/core/TablePagination&quot;;
  import TableRow from &quot;@material-ui/core/TableRow&quot;;
  import TableSortLabel from &quot;@material-ui/core/TableSortLabel&quot;;
  import Toolbar from &quot;@material-ui/core/Toolbar&quot;;
  import Typography from &quot;@material-ui/core/Typography&quot;;
  import Paper from &quot;@material-ui/core/Paper&quot;;
  import Checkbox from &quot;@material-ui/core/Checkbox&quot;;
  import IconButton from &quot;@material-ui/core/IconButton&quot;;
  import Tooltip from &quot;@material-ui/core/Tooltip&quot;;
  import DeleteIcon from &quot;@material-ui/icons/Delete&quot;;
  import FilterListIcon from &quot;@material-ui/icons/FilterList&quot;;
  import axios, { AxiosResponse } from &quot;axios&quot;;
  import { getTask } from &quot;../../service/merchants&quot;;
  
  const baseUrl = &quot;http://185.185.126.15:8080/api&quot;;
  
  interface OnboardingTaskDto {
    id?: number;
    name: string;
  }
  
  async function getTask(
    page: number,
    size: number
  ): Promise&lt;AxiosResponse&lt;OnboardingTaskDto[]&gt;&gt; {
    return await axios.get&lt;OnboardingTaskDto[]&gt;(
      `${baseUrl}/management/onboarding/task?page=${page}&amp;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&lt;T&gt;(a: T, b: T, orderBy: keyof T) {
    if (b[orderBy] &lt; a[orderBy]) {
      return -1;
    }
    if (b[orderBy] &gt; a[orderBy]) {
      return 1;
    }
    return 0;
  }
  
  type Order = &quot;asc&quot; | &quot;desc&quot;;
  
  function getComparator&lt;Key extends keyof any&gt;(
      order: Order,
      orderBy: Key
  ): (
      a: { [key in Key]: number | string },
      b: { [key in Key]: number | string }
  ) =&gt; number {
    return order === &quot;desc&quot;
        ? (a, b) =&gt; descendingComparator(a, b, orderBy)
        : (a, b) =&gt; -descendingComparator(a, b, orderBy);
  }
  
  function stableSort&lt;T&gt;(array: T[], comparator: (a: T, b: T) =&gt; number) {
    const stabilizedThis = array.map((el, index) =&gt; [el, index] as [T, number]);
    stabilizedThis.sort((a, b) =&gt; {
      const order = comparator(a[0], b[0]);
      if (order !== 0) return order;
      return a[1] - b[1];
    });
    return stabilizedThis.map((el) =&gt; el[0]);
  }
  
  interface HeadCell {
    disablePadding: boolean;
    id: keyof Data;
    label: string;
    numeric: boolean;
  }
  
  const headCells: HeadCell[] = [
    { id: &quot;id&quot;, numeric: false, disablePadding: true, label: &quot;id&quot; },
    {
      id: &quot;businessName&quot;,
      numeric: true,
      disablePadding: false,
      label: &quot;businessName&quot;,
    },
    { id: &quot;title&quot;, numeric: true, disablePadding: false, label: &quot;title&quot; },
    { id: &quot;status&quot;, numeric: true, disablePadding: false, label: &quot;status&quot; },
  ];
  
  interface EnhancedTableProps {
    classes: ReturnType&lt;typeof useStyles&gt;;
    numSelected: number;
    onRequestSort: (
        event: React.MouseEvent&lt;unknown&gt;,
        property: keyof Data
    ) =&gt; void;
    onSelectAllClick: (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; 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) =&gt; (event: React.MouseEvent&lt;unknown&gt;) =&gt; {
          onRequestSort(event, property);
        };
  
    return (
        &lt;TableHead&gt;
          &lt;TableRow&gt;
            &lt;TableCell padding=&quot;checkbox&quot;&gt;
              &lt;Checkbox
                  indeterminate={
                    numSelected &gt; 0 &amp;&amp; numSelected &lt; rowCount
                  }
                  checked={rowCount &gt; 0 &amp;&amp; numSelected === rowCount}
                  onChange={onSelectAllClick}
                  inputProps={{ &quot;aria-label&quot;: &quot;select all desserts&quot; }}
              /&gt;
            &lt;/TableCell&gt;
            {headCells.map((headCell) =&gt; (
                &lt;TableCell
                    key={headCell.id}
                    align={headCell.numeric ? &quot;right&quot; : &quot;left&quot;}
                    padding={headCell.disablePadding ? &quot;none&quot; : &quot;normal&quot;}
                    sortDirection={orderBy === headCell.id ? order : false}
                &gt;
                  &lt;TableSortLabel
                      active={orderBy === headCell.id}
                      direction={orderBy === headCell.id ? order : &quot;asc&quot;}
                      onClick={createSortHandler(headCell.id)}
                  &gt;
                    {headCell.label}
                    {orderBy === headCell.id ? (
                        &lt;span className={classes.visuallyHidden}&gt;
                                      {order === &quot;desc&quot;
                                          ? &quot;sorted descending&quot;
                                          : &quot;sorted ascending&quot;}
                                  &lt;/span&gt;
                    ) : null}
                  &lt;/TableSortLabel&gt;
                &lt;/TableCell&gt;
            ))}
          &lt;/TableRow&gt;
        &lt;/TableHead&gt;
    );
  }
  
  const useToolbarStyles = makeStyles((theme: Theme) =&gt;
      createStyles({
        root: {
          paddingLeft: theme.spacing(2),
          paddingRight: theme.spacing(1),
        },
        highlight:
            theme.palette.type === &quot;light&quot;
                ? {
                  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: &quot;1 1 100%&quot;,
        },
      })
  );
  
  interface EnhancedTableToolbarProps {
    numSelected: number;
    onClick: (e: React.MouseEvent&lt;unknown&gt;) =&gt; void;
  }
  
  const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) =&gt; {
    const classes = useToolbarStyles();
    const { numSelected } = props;
  
    return (
        &lt;Toolbar
            className={clsx(classes.root, {
              [classes.highlight]: numSelected &gt; 0,
            })}
        &gt;
          {numSelected &gt; 0 ? (
              &lt;Typography
                  className={classes.title}
                  color=&quot;inherit&quot;
                  variant=&quot;subtitle1&quot;
                  component=&quot;div&quot;
              &gt;
                {numSelected} selected
              &lt;/Typography&gt;
          ) : (
              &lt;Typography
                  className={classes.title}
                  variant=&quot;h6&quot;
                  id=&quot;tableTitle&quot;
                  component=&quot;div&quot;
              &gt;
                Customers
              &lt;/Typography&gt;
          )}
          {numSelected &gt; 0 ? (
              &lt;Tooltip title=&quot;Delete&quot;&gt;
                &lt;IconButton aria-label=&quot;delete&quot; onClick={props.onClick}&gt;
                  &lt;DeleteIcon /&gt;
                &lt;/IconButton&gt;
              &lt;/Tooltip&gt;
          ) : (
              &lt;Tooltip title=&quot;Filter list&quot;&gt;
                &lt;IconButton aria-label=&quot;filter list&quot;&gt;
                  &lt;FilterListIcon /&gt;
                &lt;/IconButton&gt;
              &lt;/Tooltip&gt;
          )}
        &lt;/Toolbar&gt;
    );
  };
  
  const useStyles = makeStyles((theme: Theme) =&gt;
      createStyles({
        root: {
          width: &quot;100%&quot;,
        },
        paper: {
          width: &quot;100%&quot;,
          marginBottom: theme.spacing(2),
        },
        table: {
          minWidth: 750,
        },
        visuallyHidden: {
          border: 0,
          clip: &quot;rect(0 0 0 0)&quot;,
          height: 1,
          margin: -1,
          overflow: &quot;hidden&quot;,
          padding: 0,
          position: &quot;absolute&quot;,
          top: 20,
          width: 1,
        },
      })
  );
  
  export default function BusinessCustomersTable() {
    const classes = useStyles();
    const [order, setOrder] = React.useState&lt;Order&gt;(&quot;asc&quot;);
    const [orderBy, setOrderBy] = React.useState&lt;keyof Data&gt;(&quot;businessName&quot;);
    const [selected, setSelected] = React.useState&lt;number[]&gt;([]);
    const [page, setPage] = React.useState(0);
    const [rowsPerPage, setRowsPerPage] = React.useState(5);
    const [rows, setRows] = useState&lt;Data[]&gt;([]);
    const [loading, setLoading] = useState(false);
  
    let updatedState: Data[] = [];
  
    // TODO - move this to API file
    const apiUrl = &quot;http://185.185.126.15:8080/api/management/onboarding/task&quot;;
  
    useEffect(() =&gt; {
      const getData = async () =&gt; {
        setLoading(true);
  
        getTask(1, 100)
            .then((resp) =&gt; {
              console.log(resp.data);
            })
            .catch((error) =&gt; {
              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 &lt; 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&lt;unknown&gt;,
        property: keyof Data
    ) =&gt; {
      const isAsc = orderBy === property &amp;&amp; order === &quot;asc&quot;;
      setOrder(isAsc ? &quot;desc&quot; : &quot;asc&quot;);
      setOrderBy(property);
    };
  
    const handleSelectAllClick = (
        event: React.ChangeEvent&lt;HTMLInputElement&gt;
    ) =&gt; {
      if (event.target.checked) {
        const newSelecteds = rows.map((n) =&gt; n.id);
        setSelected(newSelecteds);
        return;
      }
      setSelected([]);
    };
  
    const handleClick = (event: React.MouseEvent&lt;unknown&gt;, id: number) =&gt; {
      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 &gt; 0) {
        newSelected = newSelected.concat(
            selected.slice(0, selectedIndex),
            selected.slice(selectedIndex + 1)
        );
      }
  
      setSelected(newSelected);
    };
  
    const handleChangePage = (event: unknown, newPage: number) =&gt; {
      setPage(newPage);
    };
  
    const handleChangeRowsPerPage = (
        event: React.ChangeEvent&lt;HTMLInputElement&gt;
    ) =&gt; {
      setRowsPerPage(parseInt(event.target.value, 10));
      setPage(0);
    };
  
    const handleDeleteClick = async () =&gt; {
      // npm install qs
      var qs = require(&quot;qs&quot;);
  
      const response = await axios.delete(apiUrl, {
        params: {
          ids: selected,
        },
        paramsSerializer: (params) =&gt; {
          return qs.stringify(params);
        },
      });
  
      if (response.status === 204) {
        const updatedData = rows.filter(
            (row) =&gt; !selected.includes(row.id)
        ); // It'll return all data except selected ones
  
        setRows(updatedData); // reset rows to display in table.
      }
    };
  
    const isSelected = (id: number) =&gt; selected.indexOf(id) !== -1;
  
    const emptyRows =
        rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
  
    return (
        &lt;div className={classes.root}&gt;
          &lt;Paper className={classes.paper}&gt;
            &lt;EnhancedTableToolbar
                numSelected={selected.length}
                onClick={handleDeleteClick}
            /&gt;
            &lt;TableContainer&gt;
              &lt;Table
                  className={classes.table}
                  aria-labelledby=&quot;tableTitle&quot;
                  aria-label=&quot;enhanced table&quot;
              &gt;
                &lt;EnhancedTableHead
                    classes={classes}
                    numSelected={selected.length}
                    order={order}
                    orderBy={orderBy}
                    onSelectAllClick={handleSelectAllClick}
                    onRequestSort={handleRequestSort}
                    rowCount={rows.length}
                /&gt;
                &lt;TableBody&gt;
                  {loading ? (
                      &lt;div className=&quot;spinerr&quot;&gt;
                        &lt;CircularProgress /&gt;
                      &lt;/div&gt;
                  ) : null}
                  {stableSort(rows, getComparator(order, orderBy))
                      .slice(
                          page * rowsPerPage,
                          page * rowsPerPage + rowsPerPage
                      )
                      .map((row, index) =&gt; {
                        const isItemSelected = isSelected(row.id);
                        const labelId = `enhanced-table-checkbox-${index}`;
  
                        return (
                            &lt;TableRow
                                hover
                                onClick={(event) =&gt;
                                    handleClick(event, row.id)
                                }
                                role=&quot;checkbox&quot;
                                aria-checked={isItemSelected}
                                tabIndex={-1}
                                key={row.businessName}
                                selected={isItemSelected}
                            &gt;
                              &lt;TableCell padding=&quot;checkbox&quot;&gt;
                                &lt;Checkbox
                                    checked={isItemSelected}
                                    inputProps={{
                                      &quot;aria-labelledby&quot;:
                                      labelId,
                                    }}
                                /&gt;
                              &lt;/TableCell&gt;
                              &lt;TableCell
                                  component=&quot;th&quot;
                                  id={labelId}
                                  scope=&quot;row&quot;
                                  padding=&quot;none&quot;
                              &gt;
                                {row.id}
                              &lt;/TableCell&gt;
                              &lt;TableCell align=&quot;right&quot;&gt;
                                {row.businessName}
                              &lt;/TableCell&gt;
                              &lt;TableCell align=&quot;right&quot;&gt;
                                {row.title}
                              &lt;/TableCell&gt;
                              &lt;TableCell align=&quot;right&quot;&gt;
                                {row.status}
                              &lt;/TableCell&gt;
                            &lt;/TableRow&gt;
                        );
                      })}
                  {emptyRows &gt; 0 &amp;&amp; (
                      &lt;TableRow style={{ height: 53 * emptyRows }}&gt;
                        &lt;TableCell colSpan={6} /&gt;
                      &lt;/TableRow&gt;
                  )}
                &lt;/TableBody&gt;
              &lt;/Table&gt;
            &lt;/TableContainer&gt;
            &lt;TablePagination
                rowsPerPageOptions={[5, 10, 25]}
                component=&quot;div&quot;
                count={rows.length}
                rowsPerPage={rowsPerPage}
                page={page}
                onPageChange={handleChangePage}
                onRowsPerPageChange={handleChangeRowsPerPage}
            /&gt;
          &lt;/Paper&gt;
        &lt;/div&gt;
    );
  }
  </code></pre>
  <p>Sandbox: <a href="https://stackblitz.com/edit/react-ts-tnpk85?file=Hello.tsx" rel="nofollow noreferrer">https://stackblitz.com/edit/react-ts-tnpk85?file=Hello.tsx</a></p>
  <p>When data is loaded first time and I switch pages I don't see additional requests to Back end. Looks like data table rows data is loaded only once. I need to implement a lazy pagination and load current page data when I switch  page. Do you know how I can fix this?</p>