Skip to content

Commit 9736fbd

Browse files
committed
doc: add tanstack multiple paginations example
1 parent 803d3e0 commit 9736fbd

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
'use client'
2+
3+
import { CodeBlock } from '@/src/components/code-block.client'
4+
import { Querystring } from '@/src/components/querystring'
5+
import { Label } from '@/src/components/ui/label'
6+
import {
7+
Pagination,
8+
PaginationButton,
9+
PaginationContent,
10+
PaginationItem,
11+
PaginationNext,
12+
PaginationPrevious
13+
} from '@/src/components/ui/pagination'
14+
import {
15+
Select,
16+
SelectContent,
17+
SelectItem,
18+
SelectTrigger,
19+
SelectValue
20+
} from '@/src/components/ui/select'
21+
import { Separator } from '@/src/components/ui/separator'
22+
import {
23+
createParser,
24+
parseAsInteger,
25+
useQueryState,
26+
parseAsArrayOf
27+
} from 'nuqs'
28+
import { useDeferredValue, useState } from 'react'
29+
30+
const NUM_PAGES = 5
31+
32+
// The parser is zero-indexed internally,
33+
// but one-indexed when rendered in the URL,
34+
// to align with your UI and what users might expect.
35+
const paginationParser = createParser({
36+
parse(query) {
37+
const pagination = parseAsArrayOf(parseAsInteger).parse(query);
38+
39+
if (pagination === null) return null
40+
41+
const [pageIndex, pageSize] = pagination
42+
43+
return { pageIndex: pageIndex - 1, pageSize };
44+
},
45+
serialize({ pageIndex, pageSize }) {
46+
return parseAsArrayOf(parseAsInteger).serialize([pageIndex + 1, pageSize]);
47+
},
48+
eq({ pageIndex, pageSize }) {
49+
return pageIndex === 0 && pageSize === 10;
50+
},
51+
});
52+
53+
type Pagination = {pageIndex: number; pageSize: number}
54+
55+
const PaginationComponent = ({pagination, onPaginationChange}:
56+
{pagination:Pagination ;onPaginationChange: React.Dispatch<React.SetStateAction<Pagination>>}) => {
57+
const { pageIndex: page, pageSize } = pagination
58+
const setPage = (pageIndex: number) => {
59+
onPaginationChange((prev) => ({
60+
...prev, pageIndex
61+
}))
62+
}
63+
const setPageSize = (pageSize: number) => {
64+
onPaginationChange((prev) => ({
65+
...prev, pageSize
66+
}))
67+
}
68+
69+
return <div className="flex flex-wrap items-center justify-start gap-2 rounded-xl border border-dashed p-1">
70+
<Pagination className="not-prose mx-0 w-auto items-center gap-2">
71+
<PaginationContent>
72+
<PaginationItem>
73+
<PaginationPrevious
74+
disabled={page <= 0}
75+
onClick={() => setPage(Math.max(0, page - 1))}
76+
/>
77+
</PaginationItem>
78+
{Array.from({ length: NUM_PAGES }, (_, index) => (
79+
<PaginationItem key={index}>
80+
<PaginationButton
81+
isActive={page === index}
82+
onClick={() => setPage(index)}
83+
>
84+
{index + 1}
85+
</PaginationButton>
86+
</PaginationItem>
87+
))}
88+
<PaginationItem>
89+
<PaginationNext
90+
disabled={page >= NUM_PAGES - 1}
91+
onClick={() => setPage(Math.min(NUM_PAGES - 1, page + 1))}
92+
/>
93+
</PaginationItem>
94+
</PaginationContent>
95+
</Pagination>
96+
<Label className="ml-auto flex items-center gap-2">
97+
Items per page
98+
<Select
99+
value={pageSize.toFixed()}
100+
onValueChange={value => setPageSize(parseInt(value))}
101+
>
102+
<SelectTrigger className="w-24">
103+
<SelectValue placeholder="10" />
104+
</SelectTrigger>
105+
<SelectContent>
106+
<SelectItem value="10">10</SelectItem>
107+
<SelectItem value="25">25</SelectItem>
108+
<SelectItem value="50">50</SelectItem>
109+
<SelectItem value="100">100</SelectItem>
110+
</SelectContent>
111+
</Select>
112+
</Label>
113+
</div>
114+
}
115+
116+
export function TanStackTableMultiplePagination() {
117+
const [firstPaginationKey, setFirstPaginationKey] = useState('p1')
118+
const [secondPaginationKey, setSecondPaginationKey] = useState('p2')
119+
const [thirdPaginationKey, setThirdPaginationKey] = useState('p3')
120+
121+
const paginationParserWithDefaults = paginationParser.withDefault({ pageIndex: 0, pageSize: 10 })
122+
123+
const [firstPagination, setFirstPagination] = useQueryState(
124+
firstPaginationKey,
125+
paginationParserWithDefaults
126+
)
127+
const [secondPagination, setSecondPagination] = useQueryState(
128+
secondPaginationKey,
129+
paginationParserWithDefaults
130+
)
131+
const [thirdPagination, setThirdPagination] = useQueryState(
132+
thirdPaginationKey,
133+
paginationParserWithDefaults
134+
)
135+
136+
137+
const parserCode = useDeferredValue(`import {
138+
createParser,
139+
parseAsInteger,
140+
useQueryState,
141+
parseAsArrayOf
142+
} from 'nuqs'
143+
144+
// The parser is zero-indexed internally,
145+
// but one-indexed when rendered in the URL,
146+
// to align with your UI and what users might expect.
147+
const paginationParser = createParser({
148+
parse(query) {
149+
const pagination = parseAsArrayOf(parseAsInteger).parse(query);
150+
151+
if (pagination === null) return null
152+
153+
const [pageIndex, pageSize] = pagination
154+
155+
return { pageIndex: pageIndex - 1, pageSize };
156+
},
157+
serialize({ pageIndex, pageSize }) {
158+
return parseAsArrayOf(parseAsInteger).serialize([pageIndex + 1, pageSize]);
159+
},
160+
eq({ pageIndex, pageSize }) {
161+
return pageIndex === 0 && pageSize === 10;
162+
},
163+
});
164+
165+
const paginationParserWithDefaults = paginationParser.withDefault({pageIndex: 0, pageSize: 10})
166+
167+
const [firstPagination, setFirstPagination] = useQueryState(
168+
'${firstPaginationKey}',
169+
paginationParserWithDefaults
170+
)
171+
const [secondPagination, setSecondPagination] = useQueryState(
172+
'${secondPaginationKey}',
173+
paginationParserWithDefaults
174+
)
175+
const [thirdPagination, setThirdPagination] = useQueryState(
176+
'${thirdPaginationKey}',
177+
paginationParserWithDefaults
178+
)
179+
}`)
180+
181+
const internalState = useDeferredValue(`{
182+
// zero-indexed
183+
${firstPaginationKey}: ${JSON.stringify(firstPagination, null, 2)},
184+
${secondPaginationKey}: ${JSON.stringify(secondPagination, null, 2)},
185+
${thirdPaginationKey}: ${JSON.stringify(thirdPagination, null, 2)}
186+
}`)
187+
188+
return (
189+
<section>
190+
<div className='flex flex-col gap-2 p-2 border border-dashed'>
191+
<PaginationComponent pagination={firstPagination} onPaginationChange={setFirstPagination}/>
192+
<PaginationComponent pagination={secondPagination} onPaginationChange={setSecondPagination}/>
193+
<PaginationComponent pagination={thirdPagination} onPaginationChange={setThirdPagination}/>
194+
</div>
195+
<p className="mb-0">
196+
Configure and copy-paste this parser into your application:
197+
</p>
198+
<div className="flex flex-col gap-6 xl:flex-row">
199+
<CodeBlock
200+
title="search-params.pagination.ts"
201+
lang="ts"
202+
icon={
203+
<svg
204+
fill="none"
205+
viewBox="0 0 128 128"
206+
xmlns="http://www.w3.org/2000/svg"
207+
role="presentation"
208+
>
209+
<rect fill="currentColor" height="128" rx="6" width="128" />
210+
<path
211+
clipRule="evenodd"
212+
d="m74.2622 99.468v14.026c2.2724 1.168 4.9598 2.045 8.0625 2.629 3.1027.585 6.3728.877 9.8105.877 3.3503 0 6.533-.321 9.5478-.964 3.016-.643 5.659-1.702 7.932-3.178 2.272-1.476 4.071-3.404 5.397-5.786 1.325-2.381 1.988-5.325 1.988-8.8313 0-2.5421-.379-4.7701-1.136-6.6841-.758-1.9139-1.85-3.6159-3.278-5.1062-1.427-1.4902-3.139-2.827-5.134-4.0104-1.996-1.1834-4.246-2.3011-6.752-3.353-1.8352-.7597-3.4812-1.4975-4.9378-2.2134-1.4567-.7159-2.6948-1.4464-3.7144-2.1915-1.0197-.7452-1.8063-1.5341-2.3598-2.3669-.5535-.8327-.8303-1.7751-.8303-2.827 0-.9643.2476-1.8336.7429-2.6079s1.1945-1.4391 2.0976-1.9943c.9031-.5551 2.0101-.9861 3.3211-1.2929 1.311-.3069 2.7676-.4603 4.3699-.4603 1.1658 0 2.3958.0877 3.6928.263 1.296.1753 2.6.4456 3.911.8109 1.311.3652 2.585.8254 3.824 1.3806 1.238.5552 2.381 1.198 3.43 1.9285v-13.1051c-2.127-.8182-4.45-1.4245-6.97-1.819s-5.411-.5917-8.6744-.5917c-3.3211 0-6.4674.3579-9.439 1.0738-2.9715.7159-5.5862 1.8336-7.844 3.353-2.2578 1.5195-4.0422 3.4553-5.3531 5.8075-1.311 2.3522-1.9665 5.1646-1.9665 8.4373 0 4.1785 1.2017 7.7433 3.6052 10.6945 2.4035 2.9513 6.0523 5.4496 10.9466 7.495 1.9228.7889 3.7145 1.5633 5.375 2.323 1.6606.7597 3.0954 1.5486 4.3044 2.3668s2.1628 1.7094 2.8618 2.6736c.7.9643 1.049 2.06 1.049 3.2873 0 .9062-.218 1.7462-.655 2.5202s-1.1 1.446-1.9885 2.016c-.8886.57-1.9956 1.016-3.3212 1.337-1.3255.321-2.8768.482-4.6539.482-3.0299 0-6.0305-.533-9.0021-1.6-2.9715-1.066-5.7245-2.666-8.2591-4.799zm-23.5596-34.9136h18.2974v-11.5544h-51v11.5544h18.2079v51.4456h14.4947z"
213+
className="fill-background"
214+
fillRule="evenodd"
215+
/>
216+
</svg>
217+
}
218+
className="flex-grow"
219+
code={parserCode}
220+
/>
221+
<aside className="w-full space-y-4 xl:w-64">
222+
<Querystring
223+
value={`?${firstPaginationKey}=[${firstPagination.pageIndex + 1},${firstPagination.pageSize}]&${secondPaginationKey}=[${secondPagination.pageIndex + 1},${secondPagination.pageSize}]&${thirdPaginationKey}=[${thirdPagination.pageIndex + 1},${thirdPagination.pageSize}]`}
224+
/>
225+
<CodeBlock
226+
allowCopy={false}
227+
title="Internal state"
228+
code={internalState}
229+
/>
230+
<Separator className="my-8" />
231+
<div className="space-y-2">
232+
<Label htmlFor="firstKey">First pagination URL key</Label>
233+
<input
234+
id="firstKey"
235+
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
236+
value={firstPaginationKey}
237+
onChange={e => {
238+
setFirstPagination({ pageIndex: 0, pageSize: 10 })
239+
setFirstPaginationKey(e.target.value)
240+
}}
241+
placeholder="e.g., page"
242+
/>
243+
</div>
244+
<div className="space-y-2">
245+
<Label htmlFor="secondKey">Second pagination URL key</Label>
246+
<input
247+
id="secondKey"
248+
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
249+
value={secondPaginationKey}
250+
onChange={e => {
251+
setSecondPagination({ pageIndex: 0, pageSize: 10 })
252+
setSecondPaginationKey(e.target.value)
253+
}}
254+
placeholder="e.g., page"
255+
/>
256+
</div>
257+
<div className="space-y-2">
258+
<Label htmlFor="thirdKey">Second pagination URL key</Label>
259+
<input
260+
id="thirdKey"
261+
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
262+
value={thirdPaginationKey}
263+
onChange={e => {
264+
setThirdPagination({ pageIndex: 0, pageSize: 10 })
265+
setThirdPaginationKey(e.target.value)
266+
}}
267+
placeholder="e.g., page"
268+
/>
269+
</div>
270+
</aside>
271+
</div>
272+
</section>
273+
)
274+
}

packages/docs/content/docs/parsers/community/tanstack-table.mdx

+10
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,21 @@ TanStack Table stores pagination under two pieces of state:
2020
You will likely want the URL to follow your UI and be one-based for the page index:
2121

2222
import { TanStackTablePagination } from './tanstack-table.generator'
23+
import { TanStackTableMultiplePagination } from './tanstack-table-multiple.generator'
2324

2425
<Suspense>
2526
<TanStackTablePagination />
2627
</Suspense>
2728

29+
___________
30+
31+
If you’d prefer to handle multiple pagination's under a single URL or view,
32+
a custom array parser could be really helpful (URL keys should be unique in this case):
33+
34+
<Suspense>
35+
<TanStackTableMultiplePagination />
36+
</Suspense>
37+
2838
## Filtering
2939

3040
<Callout>

0 commit comments

Comments
 (0)