Last active
April 9, 2024 04:48
-
-
Save philly-vanilly/a95f72684a01439160f67d5498bc3386 to your computer and use it in GitHub Desktop.
react-table manual sort and pagination
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, useCallback } from "react"; | |
import { useTable, useSortBy, usePagination } from "react-table"; | |
import makeData from "./makeData"; | |
const serverData = makeData(10000); | |
function Table({ | |
columns, | |
data, | |
fetchData, | |
loading, | |
pageCount: controlledPageCount | |
}) { | |
const { | |
getTableProps, | |
getTableBodyProps, | |
headerGroups, | |
prepareRow, | |
page, | |
canPreviousPage, | |
canNextPage, | |
pageOptions, | |
gotoPage, | |
nextPage, | |
previousPage, | |
setPageSize, | |
state: { pageIndex, pageSize, sortBy } | |
} = useTable( | |
{ | |
columns, | |
data, | |
manualPagination: true, | |
manualSortBy: true, | |
autoResetPage: false, | |
autoResetSortBy: false, | |
pageCount: controlledPageCount | |
}, | |
useSortBy, | |
usePagination | |
); | |
useEffect(() => { | |
fetchData({ sortBy, pageIndex, pageSize }); | |
}, [sortBy, fetchData, pageIndex, pageSize]); | |
return ( | |
<> | |
<table {...getTableProps()}> | |
<thead> | |
{headerGroups.map((headerGroup) => ( | |
<tr {...headerGroup.getHeaderGroupProps()}> | |
{headerGroup.headers.map((column) => ( | |
// Add the sorting props to control sorting. For this example | |
// we can add them into the header props | |
<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> | |
); | |
})} | |
<tr> | |
{loading ? ( | |
// Use our custom loading state to show a loading indicator | |
<td colSpan="10000">Loading...</td> | |
) : ( | |
<td colSpan="10000"> | |
Showing {page.length} of ~{controlledPageCount * pageSize}{" "} | |
results | |
</td> | |
)} | |
</tr> | |
</tbody> | |
</table> | |
<div className="pagination"> | |
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}> | |
{"<<"} | |
</button>{" "} | |
<button onClick={() => previousPage()} disabled={!canPreviousPage}> | |
{"<"} | |
</button>{" "} | |
<button onClick={() => nextPage()} disabled={!canNextPage}> | |
{">"} | |
</button>{" "} | |
<button onClick={() => gotoPage(controlledPageCount - 1)} disabled={!canNextPage}> | |
{">>"} | |
</button>{" "} | |
<span> | |
Page{" "} | |
<strong> | |
{pageIndex + 1} of {pageOptions.length} | |
</strong>{" "} | |
</span> | |
<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> | |
</> | |
); | |
} | |
function App() { | |
const [data, setData] = useState([]); | |
const [loading, setLoading] = React.useState(false); | |
const [pageCount, setPageCount] = React.useState(0); | |
const fetchIdRef = React.useRef(0); | |
const sortIdRef = React.useRef(0); | |
const columns = React.useMemo( | |
() => [ | |
{ | |
Header: "First Name", | |
accessor: "firstName", | |
}, | |
{ | |
Header: "Last Name", | |
accessor: "lastName" | |
} | |
], | |
[] | |
); | |
const fetchData = useCallback(({ sortBy, pageIndex, pageSize }) => { | |
if (sortBy) { | |
// Give this sort an ID | |
const sortId = ++sortIdRef.current; | |
// Set the loading state | |
setLoading(true); | |
//simulate remove server sort | |
setTimeout(() => { | |
// Doing multisort | |
if (sortId === sortIdRef.current) { | |
let sorted = serverData.slice(); | |
sorted.sort((a, b) => { | |
for (let i = 0; i < sortBy.length; ++i) { | |
if (a[sortBy[i].id] > b[sortBy[i].id]) | |
return sortBy[i].desc ? -1 : 1; | |
if (a[sortBy[i].id] < b[sortBy[i].id]) | |
return sortBy[i].desc ? 1 : -1; | |
} | |
return 0; | |
}); | |
const startRow = pageSize * pageIndex; | |
const endRow = startRow + pageSize; | |
setData(sorted.slice(startRow, endRow)); | |
console.log(sorted.slice(0, 10)); | |
setLoading(false); | |
} | |
}, 200); | |
} else { | |
// 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 | |
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; | |
setData(serverData.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); | |
} | |
}, []); | |
return ( | |
<Table | |
columns={columns} | |
data={data} | |
fetchData={fetchData} | |
loading={loading} | |
pageCount={pageCount} | |
/> | |
); | |
} | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment