Refactor router pagination into a hook
My intention is to move this out of PaginationToolbar entirely, so that it becomes a component we can reuse in a non-URL-state setting. (I'm looking at using pagination for the wardrobe item search is why!)
This commit is contained in:
parent
9a54694e31
commit
98e89e4302
1 changed files with 50 additions and 40 deletions
|
@ -9,54 +9,29 @@ function PaginationToolbar({
|
||||||
numPerPage = 30,
|
numPerPage = 30,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
const { query, push: pushHistory } = useRouter();
|
const {
|
||||||
|
numTotalPages,
|
||||||
|
currentPageNumber,
|
||||||
|
goToPageNumber,
|
||||||
|
buildPageUrl,
|
||||||
|
} = useRouterPagination(totalCount, numPerPage);
|
||||||
|
|
||||||
const currentOffset = parseInt(query.offset) || 0;
|
const pagesAreLoaded = currentPageNumber != null && numTotalPages != null;
|
||||||
|
const hasPrevPage = pagesAreLoaded && currentPageNumber > 1;
|
||||||
|
const hasNextPage = pagesAreLoaded && currentPageNumber < numTotalPages;
|
||||||
|
|
||||||
const currentPageIndex = Math.floor(currentOffset / numPerPage);
|
const prevPageUrl = hasPrevPage ? buildPageUrl(currentPageNumber - 1) : null;
|
||||||
const currentPageNumber = currentPageIndex + 1;
|
const nextPageUrl = hasNextPage ? buildPageUrl(currentPageNumber + 1) : null;
|
||||||
const numTotalPages = totalCount ? Math.ceil(totalCount / numPerPage) : null;
|
|
||||||
|
|
||||||
const prevPageSearchParams = new URLSearchParams(query);
|
|
||||||
const prevPageOffset = currentOffset - numPerPage;
|
|
||||||
prevPageSearchParams.set("offset", prevPageOffset);
|
|
||||||
const prevPageUrl = "?" + prevPageSearchParams.toString();
|
|
||||||
|
|
||||||
const nextPageSearchParams = new URLSearchParams(query);
|
|
||||||
const nextPageOffset = currentOffset + numPerPage;
|
|
||||||
nextPageSearchParams.set("offset", nextPageOffset);
|
|
||||||
const nextPageUrl = "?" + nextPageSearchParams.toString();
|
|
||||||
|
|
||||||
// We disable the buttons if we don't know how many total items there are,
|
|
||||||
// and therefore don't know how far navigation can go. We'll additionally
|
|
||||||
// show a loading spinner if `isLoading` is true. (But it's possible the
|
|
||||||
// buttons might be enabled, even if `isLoading` is true, because maybe
|
|
||||||
// something _else_ is loading. `isLoading` is designed to tell us whether
|
|
||||||
// waiting _might_ give us the data we need!)
|
|
||||||
const prevPageIsDisabled = totalCount == null || prevPageOffset < 0;
|
|
||||||
const nextPageIsDisabled = totalCount == null || nextPageOffset >= totalCount;
|
|
||||||
|
|
||||||
const goToPageNumber = React.useCallback(
|
|
||||||
(newPageNumber) => {
|
|
||||||
const newPageIndex = newPageNumber - 1;
|
|
||||||
const newPageOffset = newPageIndex * numPerPage;
|
|
||||||
|
|
||||||
const newPageSearchParams = new URLSearchParams(query);
|
|
||||||
newPageSearchParams.set("offset", newPageOffset);
|
|
||||||
pushHistory("?" + newPageSearchParams.toString());
|
|
||||||
},
|
|
||||||
[query, pushHistory, numPerPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align="center" justify="space-between" {...props}>
|
<Flex align="center" justify="space-between" {...props}>
|
||||||
<LinkOrButton
|
<LinkOrButton
|
||||||
href={prevPageIsDisabled ? undefined : prevPageUrl}
|
href={prevPageUrl}
|
||||||
_disabled={{
|
_disabled={{
|
||||||
cursor: isLoading ? "wait" : "not-allowed",
|
cursor: isLoading ? "wait" : "not-allowed",
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
}}
|
}}
|
||||||
isDisabled={prevPageIsDisabled}
|
isDisabled={!hasPrevPage}
|
||||||
>
|
>
|
||||||
← Prev
|
← Prev
|
||||||
</LinkOrButton>
|
</LinkOrButton>
|
||||||
|
@ -75,12 +50,12 @@ function PaginationToolbar({
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<LinkOrButton
|
<LinkOrButton
|
||||||
href={nextPageIsDisabled ? undefined : nextPageUrl}
|
href={nextPageUrl}
|
||||||
_disabled={{
|
_disabled={{
|
||||||
cursor: isLoading ? "wait" : "not-allowed",
|
cursor: isLoading ? "wait" : "not-allowed",
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
}}
|
}}
|
||||||
isDisabled={nextPageIsDisabled}
|
isDisabled={!hasNextPage}
|
||||||
>
|
>
|
||||||
Next →
|
Next →
|
||||||
</LinkOrButton>
|
</LinkOrButton>
|
||||||
|
@ -88,6 +63,41 @@ function PaginationToolbar({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useRouterPagination(totalCount, numPerPage) {
|
||||||
|
const { query, push: pushHistory } = useRouter();
|
||||||
|
|
||||||
|
const currentOffset = parseInt(query.offset) || 0;
|
||||||
|
|
||||||
|
const currentPageIndex = Math.floor(currentOffset / numPerPage);
|
||||||
|
const currentPageNumber = currentPageIndex + 1;
|
||||||
|
const numTotalPages = totalCount ? Math.ceil(totalCount / numPerPage) : null;
|
||||||
|
|
||||||
|
const buildPageUrl = React.useCallback(
|
||||||
|
(newPageNumber) => {
|
||||||
|
const newParams = new URLSearchParams(query);
|
||||||
|
const newPageIndex = newPageNumber - 1;
|
||||||
|
const newOffset = newPageIndex * numPerPage;
|
||||||
|
newParams.set("offset", newOffset);
|
||||||
|
return "?" + newParams.toString();
|
||||||
|
},
|
||||||
|
[query, numPerPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const goToPageNumber = React.useCallback(
|
||||||
|
(newPageNumber) => {
|
||||||
|
pushHistory(buildPageUrl(newPageNumber));
|
||||||
|
},
|
||||||
|
[buildPageUrl, pushHistory]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
numTotalPages,
|
||||||
|
currentPageNumber,
|
||||||
|
goToPageNumber,
|
||||||
|
buildPageUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function LinkOrButton({ href, ...props }) {
|
function LinkOrButton({ href, ...props }) {
|
||||||
if (href != null) {
|
if (href != null) {
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in a new issue