1+ import { z } from "zod"
12import {
3+ Button ,
24 Container ,
35 Flex ,
46 Heading ,
@@ -11,85 +13,118 @@ import {
1113 Thead ,
1214 Tr ,
1315} from "@chakra-ui/react"
14- import { useSuspenseQuery } from "@tanstack/react-query"
15- import { createFileRoute } from "@tanstack/react-router"
16+ import { useQuery , useQueryClient } from "@tanstack/react-query"
17+ import { createFileRoute , useNavigate } from "@tanstack/react-router"
1618
17- import { Suspense } from "react"
18- import { ErrorBoundary } from "react-error-boundary"
19+ import { useEffect } from "react"
1920import { ItemsService } from "../../client"
2021import ActionsMenu from "../../components/Common/ActionsMenu"
2122import Navbar from "../../components/Common/Navbar"
2223
24+ const itemsSearchSchema = z . object ( {
25+ page : z . number ( ) . catch ( 1 ) ,
26+ } )
27+
2328export const Route = createFileRoute ( "/_layout/items" ) ( {
2429 component : Items ,
30+ validateSearch : ( search ) => itemsSearchSchema . parse ( search ) ,
2531} )
2632
27- function ItemsTableBody ( ) {
28- const { data : items } = useSuspenseQuery ( {
29- queryKey : [ "items" ] ,
30- queryFn : ( ) => ItemsService . readItems ( { } ) ,
31- } )
33+ const PER_PAGE = 5
3234
33- return (
34- < Tbody >
35- { items . data . map ( ( item ) => (
36- < Tr key = { item . id } >
37- < Td > { item . id } </ Td >
38- < Td > { item . title } </ Td >
39- < Td color = { ! item . description ? "ui.dim" : "inherit" } >
40- { item . description || "N/A" }
41- </ Td >
42- < Td >
43- < ActionsMenu type = { "Item" } value = { item } />
44- </ Td >
45- </ Tr >
46- ) ) }
47- </ Tbody >
48- )
35+ function getItemsQueryOptions ( { page } : { page : number } ) {
36+ return {
37+ queryFn : ( ) =>
38+ ItemsService . readItems ( { skip : ( page - 1 ) * PER_PAGE , limit : PER_PAGE } ) ,
39+ queryKey : [ "items" , { page } ] ,
40+ }
4941}
42+
5043function ItemsTable ( ) {
44+ const queryClient = useQueryClient ( )
45+ const { page } = Route . useSearch ( )
46+ const navigate = useNavigate ( { from : Route . fullPath } )
47+ const setPage = ( page : number ) =>
48+ navigate ( { search : ( prev ) => ( { ...prev , page } ) } )
49+
50+ const {
51+ data : items ,
52+ isPending,
53+ isPlaceholderData,
54+ } = useQuery ( {
55+ ...getItemsQueryOptions ( { page } ) ,
56+ placeholderData : ( prevData ) => prevData ,
57+ } )
58+
59+ const hasNextPage = ! isPlaceholderData && items ?. data . length === PER_PAGE
60+ const hasPreviousPage = page > 1
61+
62+ useEffect ( ( ) => {
63+ if ( hasNextPage ) {
64+ queryClient . prefetchQuery ( getItemsQueryOptions ( { page : page + 1 } ) )
65+ }
66+ } , [ page , queryClient ] )
67+
5168 return (
52- < TableContainer >
53- < Table size = { { base : "sm" , md : "md" } } >
54- < Thead >
55- < Tr >
56- < Th > ID</ Th >
57- < Th > Title</ Th >
58- < Th > Description</ Th >
59- < Th > Actions</ Th >
60- </ Tr >
61- </ Thead >
62- < ErrorBoundary
63- fallbackRender = { ( { error } ) => (
69+ < >
70+ < TableContainer >
71+ < Table size = { { base : "sm" , md : "md" } } >
72+ < Thead >
73+ < Tr >
74+ < Th > ID</ Th >
75+ < Th > Title</ Th >
76+ < Th > Description</ Th >
77+ < Th > Actions</ Th >
78+ </ Tr >
79+ </ Thead >
80+ { isPending ? (
81+ < Tbody >
82+ { new Array ( 5 ) . fill ( null ) . map ( ( _ , index ) => (
83+ < Tr key = { index } >
84+ { new Array ( 4 ) . fill ( null ) . map ( ( _ , index ) => (
85+ < Td key = { index } >
86+ < Flex >
87+ < Skeleton height = "20px" width = "20px" />
88+ </ Flex >
89+ </ Td >
90+ ) ) }
91+ </ Tr >
92+ ) ) }
93+ </ Tbody >
94+ ) : (
6495 < Tbody >
65- < Tr >
66- < Td colSpan = { 4 } > Something went wrong: { error . message } </ Td >
67- </ Tr >
96+ { items ?. data . map ( ( item ) => (
97+ < Tr key = { item . id } opacity = { isPlaceholderData ? 0.5 : 1 } >
98+ < Td > { item . id } </ Td >
99+ < Td > { item . title } </ Td >
100+ < Td color = { ! item . description ? "ui.dim" : "inherit" } >
101+ { item . description || "N/A" }
102+ </ Td >
103+ < Td >
104+ < ActionsMenu type = { "Item" } value = { item } />
105+ </ Td >
106+ </ Tr >
107+ ) ) }
68108 </ Tbody >
69109 ) }
70- >
71- < Suspense
72- fallback = {
73- < Tbody >
74- { new Array ( 5 ) . fill ( null ) . map ( ( _ , index ) => (
75- < Tr key = { index } >
76- { new Array ( 4 ) . fill ( null ) . map ( ( _ , index ) => (
77- < Td key = { index } >
78- < Flex >
79- < Skeleton height = "20px" width = "20px" />
80- </ Flex >
81- </ Td >
82- ) ) }
83- </ Tr >
84- ) ) }
85- </ Tbody >
86- }
87- >
88- < ItemsTableBody />
89- </ Suspense >
90- </ ErrorBoundary >
91- </ Table >
92- </ TableContainer >
110+ </ Table >
111+ </ TableContainer >
112+ < Flex
113+ gap = { 4 }
114+ alignItems = "center"
115+ mt = { 4 }
116+ direction = "row"
117+ justifyContent = "flex-end"
118+ >
119+ < Button onClick = { ( ) => setPage ( page - 1 ) } isDisabled = { ! hasPreviousPage } >
120+ Previous
121+ </ Button >
122+ < span > Page { page } </ span >
123+ < Button isDisabled = { ! hasNextPage } onClick = { ( ) => setPage ( page + 1 ) } >
124+ Next
125+ </ Button >
126+ </ Flex >
127+ </ >
93128 )
94129}
95130
0 commit comments