From dda7934ed8bbf0c3d507fb0335d31e53394b955f Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Thu, 27 Jun 2024 21:49:54 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Introduce=20pagination=20in=20items?= =?UTF-8?q?=20(#1239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/_layout/items.tsx | 161 ++++++++++++++++---------- 1 file changed, 98 insertions(+), 63 deletions(-) diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index 1f53d27..9946e3b 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -1,4 +1,6 @@ +import { z } from "zod" import { + Button, Container, Flex, Heading, @@ -11,85 +13,118 @@ import { Thead, Tr, } from "@chakra-ui/react" -import { useSuspenseQuery } from "@tanstack/react-query" -import { createFileRoute } from "@tanstack/react-router" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { createFileRoute, useNavigate } from "@tanstack/react-router" -import { Suspense } from "react" -import { ErrorBoundary } from "react-error-boundary" +import { useEffect } from "react" import { ItemsService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" +const itemsSearchSchema = z.object({ + page: z.number().catch(1), +}) + export const Route = createFileRoute("/_layout/items")({ component: Items, + validateSearch: (search) => itemsSearchSchema.parse(search), }) -function ItemsTableBody() { - const { data: items } = useSuspenseQuery({ - queryKey: ["items"], - queryFn: () => ItemsService.readItems({}), - }) +const PER_PAGE = 5 - return ( - - {items.data.map((item) => ( - - {item.id} - {item.title} - - {item.description || "N/A"} - - - - - - ))} - - ) +function getItemsQueryOptions({ page }: { page: number }) { + return { + queryFn: () => + ItemsService.readItems({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }), + queryKey: ["items", { page }], + } } + function ItemsTable() { + const queryClient = useQueryClient() + const { page } = Route.useSearch() + const navigate = useNavigate({ from: Route.fullPath }) + const setPage = (page: number) => + navigate({ search: (prev) => ({ ...prev, page }) }) + + const { + data: items, + isPending, + isPlaceholderData, + } = useQuery({ + ...getItemsQueryOptions({ page }), + placeholderData: (prevData) => prevData, + }) + + const hasNextPage = !isPlaceholderData && items?.data.length === PER_PAGE + const hasPreviousPage = page > 1 + + useEffect(() => { + if (hasNextPage) { + queryClient.prefetchQuery(getItemsQueryOptions({ page: page + 1 })) + } + }, [page, queryClient]) + return ( - - - - - - - - - - - ( + <> + +
IDTitleDescriptionActions
+ + + + + + + + + {isPending ? ( + + {new Array(5).fill(null).map((_, index) => ( + + {new Array(4).fill(null).map((_, index) => ( + + ))} + + ))} + + ) : ( - - - + {items?.data.map((item) => ( + + + + + + + ))} )} - > - - {new Array(5).fill(null).map((_, index) => ( - - {new Array(4).fill(null).map((_, index) => ( - - ))} - - ))} - - } - > - - - -
IDTitleDescriptionActions
+ + + +
Something went wrong: {error.message}
{item.id}{item.title} + {item.description || "N/A"} + + +
- - - -
-
+ + + + + Page {page} + + + ) }