Skip to content

Commit 4ee942b

Browse files
I-3Bfranky47
andauthored
docs: Extend next-app example to include more features (#1025)
Co-authored-by: François Best <github@francoisbest.com>
1 parent e7d1912 commit 4ee942b

8 files changed

Lines changed: 242 additions & 58 deletions

File tree

packages/examples/next-app/src/app/_components/client.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use client'
2+
3+
import { useQueryStates } from 'nuqs'
4+
import { searchParams } from '../searchParams'
5+
6+
export function Filter() {
7+
const [filter, setFilter] = useQueryStates(searchParams, {
8+
shallow: false
9+
})
10+
return (
11+
<div className="flex flex-col gap-4">
12+
<input
13+
className="border border-gray-300 dark:border-gray-600 rounded px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
14+
type="text"
15+
value={filter.search}
16+
onChange={e => setFilter({ search: e.target.value, page: 1 })}
17+
placeholder="Search users"
18+
/>
19+
20+
<div className="flex gap-2">
21+
<button
22+
onClick={() =>
23+
setFilter(prev => ({
24+
order: prev.order === 'asc' ? 'desc' : 'asc',
25+
page: 1
26+
}))
27+
}
28+
className="rounded px-4 flex-1 py-2 text-sm border border-gray-300 dark:border-gray-600 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 transition-colors"
29+
>
30+
Order:{' '}
31+
{filter.order === 'asc'
32+
? 'Ascending'
33+
: filter.order === 'desc'
34+
? 'Descending'
35+
: 'None'}
36+
</button>
37+
38+
<button
39+
onClick={() => setFilter(null)}
40+
className="rounded px-4 py-2 text-sm border border-red-400 bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-800 dark:text-red-100 dark:hover:bg-red-700 transition-colors"
41+
>
42+
Reset
43+
</button>
44+
</div>
45+
</div>
46+
)
47+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client'
2+
import Link from 'next/link'
3+
import { useSearchParams } from 'next/navigation'
4+
import { useQueryStates } from 'nuqs'
5+
import { searchParams, serialize } from '../searchParams'
6+
7+
export function Pagination({ total }: { total: number }) {
8+
const [{ page, limit }] = useQueryStates(searchParams)
9+
const totalPages = Math.ceil(total / limit)
10+
const currentSearchParams = useSearchParams()
11+
return (
12+
<nav className="mt-2 flex items-center gap-2 mx-auto">
13+
{Array.from({ length: totalPages }, (_, i) => {
14+
const p = i + 1
15+
const isActive = p === page
16+
return (
17+
<Link
18+
key={p}
19+
href={serialize(currentSearchParams, { page: p })}
20+
className={`rounded border px-3 py-1 text-sm transition-colors ${
21+
isActive
22+
? 'bg-gray-800 text-white dark:bg-gray-200 dark:text-black'
23+
: 'hover:bg-gray-100 dark:hover:bg-gray-800'
24+
}`}
25+
>
26+
{p}
27+
</Link>
28+
)
29+
})}
30+
</nav>
31+
)
32+
}

packages/examples/next-app/src/app/_components/server.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { getUsers } from '@/data'
2+
import { searchParamsCache } from '../searchParams'
3+
import { Pagination } from './pagination'
4+
5+
export async function UsersList() {
6+
const params = searchParamsCache.all()
7+
const users = await getUsers(params)
8+
9+
return (
10+
<div className="w-full flex flex-col gap-4">
11+
<ul className="w-full max-w-sm divide-y divide-gray-200 dark:divide-gray-700 text-center sm:text-left">
12+
{users.data.map(user => (
13+
<li key={user.id} className="py-2">
14+
{user.name}
15+
</li>
16+
))}
17+
</ul>
18+
<Pagination total={users.total} />
19+
</div>
20+
)
21+
}

packages/examples/next-app/src/app/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import type { SearchParams } from 'next/dist/server/request/search-params'
22
import Image from 'next/image'
33
import { Suspense } from 'react'
4-
import { Client } from './_components/client'
5-
import { Server } from './_components/server'
4+
import { Filter } from './_components/filter'
5+
import { UsersList } from './_components/users-list'
6+
import { searchParamsCache } from './searchParams'
67

78
type PageProps = {
89
searchParams: Promise<SearchParams>
910
}
1011

1112
export default async function Home({ searchParams }: PageProps) {
13+
await searchParamsCache.parse(searchParams)
1214
return (
1315
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
1416
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
@@ -21,10 +23,10 @@ export default async function Home({ searchParams }: PageProps) {
2123
priority
2224
/>
2325
<Suspense>
24-
<Server searchParams={searchParams} />
26+
<Filter />
2527
</Suspense>
2628
<Suspense>
27-
<Client />
29+
<UsersList />
2830
</Suspense>
2931
<a
3032
className="w-full rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
import { createLoader, parseAsInteger, parseAsString } from 'nuqs/server'
1+
import {
2+
createSearchParamsCache,
3+
createSerializer,
4+
parseAsInteger,
5+
parseAsString,
6+
parseAsStringEnum
7+
} from 'nuqs/server'
28

39
export const searchParams = {
4-
name: parseAsString.withDefault(''),
5-
count: parseAsInteger.withDefault(0)
10+
page: parseAsInteger.withDefault(1),
11+
limit: parseAsInteger.withDefault(5),
12+
order: parseAsStringEnum(['asc', 'desc']),
13+
search: parseAsString.withDefault('')
614
}
715

8-
export const loadSearchParams = createLoader(searchParams)
16+
export const searchParamsCache = createSearchParamsCache(searchParams)
17+
18+
export const serialize = createSerializer(searchParams)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
export const users = [
2+
{
3+
id: 1,
4+
name: 'John Doe'
5+
},
6+
{
7+
id: 2,
8+
name: 'Jane Doe'
9+
},
10+
{
11+
id: 3,
12+
name: 'Ziad El-Sayed'
13+
},
14+
{
15+
id: 4,
16+
name: 'Alice Johnson'
17+
},
18+
{
19+
id: 5,
20+
name: 'Bob Brown'
21+
},
22+
{
23+
id: 6,
24+
name: 'Charlie Davis'
25+
},
26+
{
27+
id: 7,
28+
name: 'Diana Evans'
29+
},
30+
{
31+
id: 8,
32+
name: 'Ethan Harris'
33+
},
34+
{
35+
id: 9,
36+
name: 'Fiona Clark'
37+
},
38+
{
39+
id: 10,
40+
name: 'George Miller'
41+
},
42+
{
43+
id: 11,
44+
name: 'Hannah Wilson'
45+
},
46+
{
47+
id: 12,
48+
name: 'Ian Taylor'
49+
},
50+
{
51+
id: 13,
52+
name: 'Karen Anderson'
53+
},
54+
{
55+
id: 14,
56+
name: 'Liam Thomas'
57+
},
58+
{
59+
id: 15,
60+
name: 'Mia Rodriguez'
61+
},
62+
{
63+
id: 16,
64+
name: 'Noah Martinez'
65+
},
66+
{
67+
id: 17,
68+
name: 'Olivia Lee'
69+
},
70+
{
71+
id: 18,
72+
name: 'Peter Walker'
73+
},
74+
{
75+
id: 19,
76+
name: 'Quinn Young'
77+
},
78+
{
79+
id: 20,
80+
name: 'Riley King'
81+
}
82+
]
83+
84+
export interface GetUsersOptions {
85+
page: number
86+
limit: number
87+
order: 'asc' | 'desc' | null
88+
search: string
89+
}
90+
91+
export async function getUsers({
92+
page,
93+
limit,
94+
order,
95+
search
96+
}: GetUsersOptions) {
97+
let records = users
98+
99+
if (search.trim()) {
100+
const term = search.toLowerCase()
101+
records = records.filter(item => item.name.toLowerCase().includes(term))
102+
}
103+
104+
if (order) {
105+
records = [...records].sort((a, b) => {
106+
const valueA = a.name
107+
const valueB = b.name
108+
if (valueA < valueB) return order === 'asc' ? -1 : 1
109+
if (valueA > valueB) return order === 'asc' ? 1 : -1
110+
return 0
111+
})
112+
}
113+
114+
const total = records.length
115+
const start = (page - 1) * limit
116+
const paginated = records.slice(start, start + limit)
117+
118+
return {
119+
total,
120+
data: paginated
121+
}
122+
}

0 commit comments

Comments
 (0)