Skip to content

Commit 9bb8318

Browse files
authored
docs(solid-router): deferred-data example (#5838)
* docs(solid-router): deferred-data example * add to list in docs
1 parent 6365578 commit 9bb8318

File tree

12 files changed

+410
-0
lines changed

12 files changed

+410
-0
lines changed

docs/router/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,10 @@
650650
"label": "Authenticated Routes",
651651
"to": "framework/solid/examples/authenticated-routes"
652652
},
653+
{
654+
"label": "Deferred Data",
655+
"to": "framework/solid/examples/deferred-data"
656+
},
653657
{
654658
"label": "View Transitions",
655659
"to": "framework/solid/examples/view-transitions"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"files.watcherExclude": {
3+
"**/routeTree.gen.ts": true
4+
},
5+
"search.exclude": {
6+
"**/routeTree.gen.ts": true
7+
},
8+
"files.readonlyInclude": {
9+
"**/routeTree.gen.ts": true
10+
}
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn`
6+
- `npm start` or `yarn start`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "tanstack-router-solid-example-deferred-data",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port 3000",
7+
"build": "vite build && tsc --noEmit",
8+
"serve": "vite preview",
9+
"start": "vite"
10+
},
11+
"dependencies": {
12+
"@tailwindcss/postcss": "^4.1.15",
13+
"@tanstack/solid-router": "^1.135.2",
14+
"@tanstack/solid-router-devtools": "^1.135.2",
15+
"postcss": "^8.5.1",
16+
"solid-js": "^1.9.10",
17+
"redaxios": "^0.5.1",
18+
"tailwindcss": "^4.1.15",
19+
"zod": "^3.24.2"
20+
},
21+
"devDependencies": {
22+
"vite-plugin-solid": "^2.11.10",
23+
"typescript": "^5.7.2",
24+
"vite": "^7.1.7"
25+
}
26+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
plugins: {
3+
'@tailwindcss/postcss': {},
4+
},
5+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import { render } from 'solid-js/web'
2+
import { Suspense } from 'solid-js'
3+
import {
4+
Await,
5+
ErrorComponent,
6+
Link,
7+
MatchRoute,
8+
Outlet,
9+
RouterProvider,
10+
createRootRoute,
11+
createRoute,
12+
createRouter,
13+
defer,
14+
} from '@tanstack/solid-router'
15+
import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
16+
import axios from 'redaxios'
17+
import type { ErrorComponentProps } from '@tanstack/solid-router'
18+
import './styles.css'
19+
20+
type PostType = {
21+
id: string
22+
title: string
23+
body: string
24+
}
25+
26+
type CommentType = {
27+
id: string
28+
postId: string
29+
name: string
30+
email: string
31+
body: string
32+
}
33+
34+
const fetchPosts = async () => {
35+
console.info('Fetching posts...')
36+
await new Promise((r) => setTimeout(r, 100))
37+
return axios
38+
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
39+
.then((r) => r.data.slice(0, 10))
40+
}
41+
42+
const fetchPost = async (postId: string) => {
43+
console.info(`Fetching post with id ${postId}...`)
44+
45+
const commentsPromise = new Promise((r) => setTimeout(r, 2000))
46+
.then(() =>
47+
axios.get<Array<CommentType>>(
48+
`https://jsonplaceholder.typicode.com/comments?postId=${postId}`,
49+
),
50+
)
51+
.then((r) => r.data)
52+
53+
const post = await new Promise((r) => setTimeout(r, 1000))
54+
.then(() =>
55+
axios.get<PostType>(
56+
`https://jsonplaceholder.typicode.com/posts/${postId}`,
57+
),
58+
)
59+
.catch((err) => {
60+
if (err.status === 404) {
61+
throw new NotFoundError(`Post with id "${postId}" not found!`)
62+
}
63+
throw err
64+
})
65+
.then((r) => r.data)
66+
67+
return {
68+
post,
69+
commentsPromise: defer(commentsPromise),
70+
}
71+
}
72+
73+
function Spinner({ show, wait }: { show?: boolean; wait?: `delay-${number}` }) {
74+
return (
75+
<div
76+
class={`inline-block animate-spin px-3 transition ${
77+
(show ?? true)
78+
? `opacity-1 duration-500 ${wait ?? 'delay-300'}`
79+
: 'duration-500 opacity-0 delay-0'
80+
}`}
81+
>
82+
83+
</div>
84+
)
85+
}
86+
87+
const rootRoute = createRootRoute({
88+
component: RootComponent,
89+
})
90+
91+
function RootComponent() {
92+
return (
93+
<>
94+
<div class="p-2 flex gap-2 text-lg">
95+
<Link
96+
to="/"
97+
activeProps={{
98+
class: 'font-bold',
99+
}}
100+
activeOptions={{ exact: true }}
101+
>
102+
Home
103+
</Link>{' '}
104+
<Link
105+
to="/posts"
106+
activeProps={{
107+
class: 'font-bold',
108+
}}
109+
>
110+
Posts
111+
</Link>
112+
</div>
113+
<hr />
114+
<Outlet />
115+
{/* Start rendering router matches */}
116+
<TanStackRouterDevtools position="bottom-right" />
117+
</>
118+
)
119+
}
120+
121+
const indexRoute = createRoute({
122+
getParentRoute: () => rootRoute,
123+
path: '/',
124+
}).update({
125+
component: IndexComponent,
126+
})
127+
128+
function IndexComponent() {
129+
return (
130+
<div class="p-2">
131+
<h3>Welcome Home!</h3>
132+
</div>
133+
)
134+
}
135+
136+
const postsRoute = createRoute({
137+
getParentRoute: () => rootRoute,
138+
path: 'posts',
139+
loader: fetchPosts,
140+
component: PostsComponent,
141+
})
142+
143+
function PostsComponent() {
144+
const posts = postsRoute.useLoaderData()
145+
146+
return (
147+
<div class="p-2 flex gap-2">
148+
<ul class="list-disc pl-4">
149+
{[...posts(), { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
150+
(post) => {
151+
return (
152+
<li class="whitespace-nowrap">
153+
<Link
154+
to={postRoute.to}
155+
params={{
156+
postId: post.id,
157+
}}
158+
class="flex py-1 text-blue-600 hover:opacity-75 gap-2 items-center"
159+
activeProps={{ class: 'font-bold underline' }}
160+
>
161+
<div>{post.title.substring(0, 20)}</div>
162+
<MatchRoute
163+
to={postRoute.to}
164+
params={{
165+
postId: post.id,
166+
}}
167+
pending
168+
>
169+
{(match) => {
170+
return <Spinner show={!!match} wait="delay-0" />
171+
}}
172+
</MatchRoute>
173+
</Link>
174+
</li>
175+
)
176+
},
177+
)}
178+
</ul>
179+
<hr />
180+
<Outlet />
181+
</div>
182+
)
183+
}
184+
185+
class NotFoundError extends Error {}
186+
187+
const postRoute = createRoute({
188+
getParentRoute: () => postsRoute,
189+
path: '$postId',
190+
loader: async ({ params: { postId } }) => fetchPost(postId),
191+
errorComponent: PostErrorComponent,
192+
component: PostComponent,
193+
})
194+
195+
function PostErrorComponent({ error }: ErrorComponentProps) {
196+
if (error instanceof NotFoundError) {
197+
return <div>{error.message}</div>
198+
}
199+
200+
return <ErrorComponent error={error} />
201+
}
202+
203+
function PostComponent() {
204+
const loaderData = postRoute.useLoaderData()
205+
206+
return (
207+
<div class="space-y-2">
208+
<h4 class="text-xl font-bold underline">{loaderData().post.title}</h4>
209+
<div class="text-sm">{loaderData().post.body}</div>
210+
<Suspense
211+
fallback={
212+
<div class="flex items-center gap-2">
213+
<Spinner />
214+
Loading comments...
215+
</div>
216+
}
217+
>
218+
<Await promise={loaderData().commentsPromise}>
219+
{(comments) => {
220+
return (
221+
<div class="space-y-2">
222+
<h5 class="text-lg font-bold underline">Comments</h5>
223+
{comments.map((comment) => {
224+
return (
225+
<div>
226+
<h6 class="text-md font-bold">{comment.name}</h6>
227+
<div class="text-sm italic opacity-50">
228+
{comment.email}
229+
</div>
230+
<div class="text-sm">{comment.body}</div>
231+
</div>
232+
)
233+
})}
234+
</div>
235+
)
236+
}}
237+
</Await>
238+
</Suspense>
239+
</div>
240+
)
241+
}
242+
243+
const routeTree = rootRoute.addChildren([
244+
postsRoute.addChildren([postRoute]),
245+
indexRoute,
246+
])
247+
248+
// Set up a Router instance
249+
const router = createRouter({
250+
routeTree,
251+
defaultPreload: 'intent',
252+
scrollRestoration: true,
253+
})
254+
255+
// Register things for typesafety
256+
declare module '@tanstack/solid-router' {
257+
interface Register {
258+
router: typeof router
259+
}
260+
}
261+
262+
const rootElement = document.getElementById('app')!
263+
264+
if (!rootElement.innerHTML) {
265+
render(() => <RouterProvider router={router} />, rootElement)
266+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@import 'tailwindcss';
2+
3+
@layer base {
4+
*,
5+
::after,
6+
::before,
7+
::backdrop,
8+
::file-selector-button {
9+
border-color: var(--color-gray-200, currentcolor);
10+
}
11+
}
12+
13+
html {
14+
color-scheme: light dark;
15+
}
16+
* {
17+
@apply border-gray-200 dark:border-gray-800;
18+
}
19+
body {
20+
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"esModuleInterop": true,
5+
"jsx": "preserve",
6+
"jsxImportSource": "solid-js",
7+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
8+
"skipLibCheck": true
9+
}
10+
}

0 commit comments

Comments
 (0)