Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement server-side pagination using react-table?

I am new to react-table. I am trying to implement server-side pagination but I am not getting the logic on how to detect the page change in the new react-table version. I am using fetch data proper I am unable to detect the change. Each time I click on the Next button I should be able to change the offset value in the API endpoint in the increments of 20 to fetch new data. I am unable to perform this operation. Kindly help.

import React, { useEffect, useState, useMemo } from 'react'
import { URLs } from "../../../Config/url";
import cookie from 'react-cookies';
import "./OrderManagementScreen.css"
import { useTable, usePagination, useSortBy } from 'react-table';
import styled from 'styled-components';


const Styles = styled.div`
padding: 1rem;

table {
  border-spacing: 0;
  border: 1px solid lightgray;
  width: 100%;
  text-align: "center" !important;

  tr {
    :last-child {
      td {
        border-bottom: 0;
        text-align: "center" !important;
      }
    }
  }
  th {
    padding: 3px;
    box-shadow: 0px 5px 7px 2px lightgrey;
  }
  td {
    padding: 5px;
  }
  th,
  td {
    margin: 0;
    text-align: "center";
    border-bottom: 1px solid #73737361;
    border-right: 1px solid #73737361;

    :last-child {
      border-right: 0;
    }
  }
}
.pagination {
}
`;

const WrapperTable = styled.div`
background: #ffffff;
box-shadow: 3px 3px 2px 0px rgb(162 161 161 / 75%) !important;
border-radius: 5px;
`


const Table = ({ columns, data }) => {
  // Use the state and functions returned from useTable to build your UI
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,

    page, // Instead of using 'rows', we'll use page,
    // which has only the rows for the active page

    // The rest of these things are super handy, too ;)
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    state: { pageIndex, pageSize, sortBy },
  } = useTable(
    {
      columns,
      data,
      initialState: { pageIndex: 0 },
    },
    useSortBy,
    usePagination
  );
  // const sorted = column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : "";
  // const sorted = column.isSorted ? (column.isSortedDesc ? {borderTop:"1px solid "} :{borderTop:"1px solid "}) : "";
  // Render the UI for your table
  return (
    <>
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <th {...column.getHeaderProps(column.getSortByToggleProps())}>
                  {column.render("Header")}
                  {/* Add a sort direction indicator */}
                  <span>
                    {column.isSorted
                      ? column.isSortedDesc
                        ? " 🔽"
                        : " 🔼"
                      : ""}
                  </span>
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map((row, i) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
      {/* 
      Pagination can be built however you'd like. 
      This is just a very basic UI implementation:
      */}
      <div className="pagination">
        {/* <button
          className="pagination-btn"
          onClick={() => gotoPage(0)}
          disabled={!canPreviousPage}
        >
          First
        </button> */}
        <button
          className="pagination-btn"
          onClick={() => previousPage()}
          disabled={!canPreviousPage}
        >
          Previous
        </button>
        <span className="pagination-btn text-center">
          Page{" "}
          <strong>
            {pageIndex + 1} of {pageOptions.length}
          </strong>{" "}
        </span>
        <button
          className="pagination-btn"
          onClick={() => nextPage()}
          disabled={!canNextPage}
        >
          Next
        </button>
        {/* <button
        className="pagination-btn"
          onClick={() => gotoPage(pageCount - 1)}
          disabled={!canNextPage}
        >
          Last
        </button> */}

        {/* <span>
          | Go to page:{' '}
          <input
            type="number"
            defaultValue={pageIndex + 1}
            onChange={e => {
              const page = e.target.value ? Number(e.target.value) - 1 : 0
              gotoPage(page)
            }}
            style={{ width: '100px' }}
          />
        </span> */}
        {/* <select
          value={pageSize}
          onChange={e => {
            setPageSize(Number(e.target.value))
          }}
        >
          {[10, 20, 30, 40, 50].map(pageSize => (
            <option key={pageSize} value={pageSize}>
              Show {pageSize}
            </option>
          ))}
        </select> */}
      </div>
    </>
  );
};

const OrderManagementScreen = () => {
  const token = cookie.load("userObj").data.token;
  //orderid, outletname, area, distributor, ordervalue, outlet type, discount group, salesofficer,order
  const [tableData, SetData] = useState([]);
  const [loading, setLoading] = React.useState(false);
  const fetchIdRef = React.useRef(0);
  const sortIdRef = React.useRef(0);

  const columns = React.useMemo(
    () => [
      {
        Header: "Order Id",
        accessor: "id",
      },
      {
        Header: "Outlet Name",
        id: "outlet_detail",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.name);
          });
          return output.join(', ');
        }
      },
      {
        Header: "Area",
        id: "area",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.area__name);
          });
          return output.join(', ');
        }
      },
      {
        Header: "Distributor",
        id: "distributor",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.distributor_name);
          });
          return output.join(', ');
        }
      },
      {
        Header: "Order Value",
        accessor: "total_price",
      },
      {
        Header: "Outlet Type",
        id: "outlet_type__name",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.final_value);
          });
          return output.join(', ');
        }
      },
      {
        Header: "Discount Group",
        id: "discount__name",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.discount__name);
          });
          return output.join(', ');
        }
      },
      {
        Header: "Sales Officer",
        id: "sales_officer",
        accessor: data => {
          let output = [];
          data.outlet_detail.map(item => {
            return output.push(item.by_user__username);
          });
          return output.join(', ');
        }
      }
    ],
    []
  );
  const listdata = async () => {
    const response = await fetch(`${URLs.orderUrl}?limit=20&offset=0`, {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Token ${token}`
      }
    })

    const data = await response.json();
    SetData(data);
  }


  const fetchData = React.useCallback(({ pageSize, pageIndex, sortBy }) => {
    // This will get called when the table needs new data
    // You could fetch your data from literally anywhere,
    // even a server. But for this example, we'll just fake it.

    // Give this fetch an ID
  console.log(pageIndex);
    console.log(pageSize);
    const fetchId = ++fetchIdRef.current;

    // Set the loading state
    setLoading(true);

    // We'll even set a delay to simulate a server here
    setTimeout(() => {
      // Only update the data if this is the latest fetch
      if (fetchId === fetchIdRef.current) {
        const startRow = pageSize * pageIndex;
        
        const endRow = startRow + pageSize;
        if (sortBy.length === 0) {
          SetData(tableData.sort().slice(startRow, endRow));
        } else {
          SetData(
            tableData
              .sort((a, b) => {
                const field = sortBy[0].id;
                const desc = sortBy[0].desc;
                if (a[field] < b[field]) {
                  return desc ? -1 : 1;
                }
                if (a[field] > b[field]) {
                  return desc ? 1 : -1;
                }
                return 0;
              })
              .slice(startRow, endRow)
          );
        }

        // Your server could send back total page count.
        // For now we'll just fake it, too
        // setPageCount(Math.ceil(serverData.length / pageSize));

        setLoading(false);
      }
    }, 1000);
  }, []);

  useEffect(() => {
    listdata();
  }, [])
  return (
    <div className="p-3 text-center">
      <h4>Order Management</h4>
      <WrapperTable>
        <Styles>
          <Table columns={columns} fetchData={fetchData} data={tableData} />
        </Styles>
      </WrapperTable>
    </div>
  )
}

export default OrderManagementScreen;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
like image 586
Sriharsha K Avatar asked Dec 10 '20 02:12

Sriharsha K


People also ask

Should pagination be server side?

The server needs to support pagination, and then the client-side is able to request data sets with specified page size, page index, and possibly sorting orders and filtering criteria.


1 Answers

First, you need to understand the basic difference between client-side pagination and server-side pagination.

In client-side pagination, we already have all the data for all the pages which we need to display in the table that means we know the total count as well (totalcount=pagesize*number of pages).
Now compare this with server-side pagination. We shall be getting the slice of data which we request means if we are keeping page size as 10 and we have 100 data at our server but since we requested 10 so we'll only get 10 items. Then how will the pagination component know what will be the total number of pages which he needs to display?
That's why we need a total count from the server as well when we are fetching the data. But wait, do we need it every time? Well, it depends on your use-case. In general, we need the total count either for the first time or in case we are doing any find or filter.

Now coming to your solution-
In react-table if we did not explicitly set the flag manualPagination as true then it will process the number of pages based of your supplied data and the pagesize so it will auto handle the pagination. So we need to make this manualPagination as true in options we passed to useTable and also we need to supply the total number of pages that is pageCount. So this will be something like

useTable(
    {
      columns,
      data,
      initialState: { pageIndex: 0 },
    },
    useSortBy,
    usePagination,
    manualPagination: true,
    pageCount: (totalcount/pagesize),//you can handle this calculation in your fetchdata method
  );

and then add your fetch data call inside a new useEffect with your pageindex and the pagesize as dependencies

React.useEffect(() => {
    fetchData({ pageIndex, pageSize })
  }, [fetchData, pageIndex, pageSize])

I hope this will solve your issue. This is also well explained in the react-table documentation with proper codeshare example. Check here

like image 71
Avnish Singh Awi Avatar answered Oct 23 '22 00:10

Avnish Singh Awi