mirror of
https://github.com/Dokploy/examples.git
synced 2026-06-21 07:05:22 +02:00
feat(tanstack): initialize TanStack project with routing, API, and error handling components
This commit is contained in:
139
tanstack/src/routes/__root.tsx
Normal file
139
tanstack/src/routes/__root.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
HeadContent,
|
||||
Link,
|
||||
Outlet,
|
||||
Scripts,
|
||||
createRootRoute,
|
||||
} from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
import * as React from 'react'
|
||||
import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
|
||||
import { NotFound } from '~/components/NotFound'
|
||||
import appCss from '~/styles/app.css?url'
|
||||
import { seo } from '~/utils/seo'
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
charSet: 'utf-8',
|
||||
},
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1',
|
||||
},
|
||||
...seo({
|
||||
title:
|
||||
'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
|
||||
description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
|
||||
}),
|
||||
],
|
||||
links: [
|
||||
{ rel: 'stylesheet', href: appCss },
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '180x180',
|
||||
href: '/apple-touch-icon.png',
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '32x32',
|
||||
href: '/favicon-32x32.png',
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '16x16',
|
||||
href: '/favicon-16x16.png',
|
||||
},
|
||||
{ rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
|
||||
{ rel: 'icon', href: '/favicon.ico' },
|
||||
],
|
||||
}),
|
||||
errorComponent: (props) => {
|
||||
return (
|
||||
<RootDocument>
|
||||
<DefaultCatchBoundary {...props} />
|
||||
</RootDocument>
|
||||
)
|
||||
},
|
||||
notFoundComponent: () => <NotFound />,
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<RootDocument>
|
||||
<Outlet />
|
||||
</RootDocument>
|
||||
)
|
||||
}
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
<div className="p-2 flex gap-2 text-lg">
|
||||
<Link
|
||||
to="/"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
activeOptions={{ exact: true }}
|
||||
>
|
||||
Home
|
||||
</Link>{' '}
|
||||
<Link
|
||||
to="/posts"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Posts
|
||||
</Link>{' '}
|
||||
<Link
|
||||
to="/users"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Users
|
||||
</Link>{' '}
|
||||
<Link
|
||||
to="/route-a"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Pathless Layout
|
||||
</Link>{' '}
|
||||
<Link
|
||||
to="/deferred"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Deferred
|
||||
</Link>{' '}
|
||||
<Link
|
||||
// @ts-expect-error
|
||||
to="/this-route-does-not-exist"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
This Route Does Not Exist
|
||||
</Link>
|
||||
</div>
|
||||
<hr />
|
||||
{children}
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
16
tanstack/src/routes/_pathlessLayout.tsx
Normal file
16
tanstack/src/routes/_pathlessLayout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_pathlessLayout')({
|
||||
component: LayoutComponent,
|
||||
})
|
||||
|
||||
function LayoutComponent() {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div className="border-b">I'm a layout</div>
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
tanstack/src/routes/_pathlessLayout/_nested-layout.tsx
Normal file
34
tanstack/src/routes/_pathlessLayout/_nested-layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({
|
||||
component: LayoutComponent,
|
||||
})
|
||||
|
||||
function LayoutComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div>I'm a nested layout</div>
|
||||
<div className="flex gap-2 border-b">
|
||||
<Link
|
||||
to="/route-a"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Go to route A
|
||||
</Link>
|
||||
<Link
|
||||
to="/route-b"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Go to route B
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')(
|
||||
{
|
||||
component: LayoutAComponent,
|
||||
},
|
||||
)
|
||||
|
||||
function LayoutAComponent() {
|
||||
return <div>I'm A!</div>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')(
|
||||
{
|
||||
component: LayoutBComponent,
|
||||
},
|
||||
)
|
||||
|
||||
function LayoutBComponent() {
|
||||
return <div>I'm B!</div>
|
||||
}
|
||||
24
tanstack/src/routes/api/users.$id.ts
Normal file
24
tanstack/src/routes/api/users.$id.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { json } from '@tanstack/react-start'
|
||||
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
||||
import axios from 'redaxios'
|
||||
import type { User } from '../../utils/users'
|
||||
|
||||
export const APIRoute = createAPIFileRoute('/api/users/$id')({
|
||||
GET: async ({ request, params }) => {
|
||||
console.info(`Fetching users by id=${params.id}... @`, request.url)
|
||||
try {
|
||||
const res = await axios.get<User>(
|
||||
'https://jsonplaceholder.typicode.com/users/' + params.id,
|
||||
)
|
||||
|
||||
return json({
|
||||
id: res.data.id,
|
||||
name: res.data.name,
|
||||
email: res.data.email,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
},
|
||||
})
|
||||
17
tanstack/src/routes/api/users.ts
Normal file
17
tanstack/src/routes/api/users.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { json } from '@tanstack/react-start'
|
||||
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
||||
import axios from 'redaxios'
|
||||
import type { User } from '../../utils/users'
|
||||
|
||||
export const APIRoute = createAPIFileRoute('/api/users')({
|
||||
GET: async ({ request }) => {
|
||||
console.info('Fetching users... @', request.url)
|
||||
const res = await axios.get<Array<User>>(
|
||||
'https://jsonplaceholder.typicode.com/users',
|
||||
)
|
||||
|
||||
const list = res.data.slice(0, 10)
|
||||
|
||||
return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
|
||||
},
|
||||
})
|
||||
62
tanstack/src/routes/deferred.tsx
Normal file
62
tanstack/src/routes/deferred.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Await, createFileRoute } from '@tanstack/react-router'
|
||||
import { createServerFn } from '@tanstack/react-start'
|
||||
import { Suspense, useState } from 'react'
|
||||
|
||||
const personServerFn = createServerFn({ method: 'GET' })
|
||||
.validator((d: string) => d)
|
||||
.handler(({ data: name }) => {
|
||||
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
||||
})
|
||||
|
||||
const slowServerFn = createServerFn({ method: 'GET' })
|
||||
.validator((d: string) => d)
|
||||
.handler(async ({ data: name }) => {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/deferred')({
|
||||
loader: async () => {
|
||||
return {
|
||||
deferredStuff: new Promise<string>((r) =>
|
||||
setTimeout(() => r('Hello deferred!'), 2000),
|
||||
),
|
||||
deferredPerson: slowServerFn({ data: 'Tanner Linsley' }),
|
||||
person: await personServerFn({ data: 'John Doe' }),
|
||||
}
|
||||
},
|
||||
component: Deferred,
|
||||
})
|
||||
|
||||
function Deferred() {
|
||||
const [count, setCount] = useState(0)
|
||||
const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div data-testid="regular-person">
|
||||
{person.name} - {person.randomNumber}
|
||||
</div>
|
||||
<Suspense fallback={<div>Loading person...</div>}>
|
||||
<Await
|
||||
promise={deferredPerson}
|
||||
children={(data) => (
|
||||
<div data-testid="deferred-person">
|
||||
{data.name} - {data.randomNumber}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense fallback={<div>Loading stuff...</div>}>
|
||||
<Await
|
||||
promise={deferredStuff}
|
||||
children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
|
||||
/>
|
||||
</Suspense>
|
||||
<div>Count: {count}</div>
|
||||
<div>
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
13
tanstack/src/routes/index.tsx
Normal file
13
tanstack/src/routes/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
})
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<h3>Welcome Home!!!</h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
tanstack/src/routes/posts.$postId.tsx
Normal file
34
tanstack/src/routes/posts.$postId.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router'
|
||||
import { fetchPost } from '../utils/posts'
|
||||
import { NotFound } from '~/components/NotFound'
|
||||
import { PostErrorComponent } from '~/components/PostError'
|
||||
|
||||
export const Route = createFileRoute('/posts/$postId')({
|
||||
loader: ({ params: { postId } }) => fetchPost({ data: postId }),
|
||||
errorComponent: PostErrorComponent,
|
||||
component: PostComponent,
|
||||
notFoundComponent: () => {
|
||||
return <NotFound>Post not found</NotFound>
|
||||
},
|
||||
})
|
||||
|
||||
function PostComponent() {
|
||||
const post = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
||||
<div className="text-sm">{post.body}</div>
|
||||
<Link
|
||||
to="/posts/$postId/deep"
|
||||
params={{
|
||||
postId: post.id,
|
||||
}}
|
||||
activeProps={{ className: 'text-black font-bold' }}
|
||||
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||
>
|
||||
Deep View
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
tanstack/src/routes/posts.index.tsx
Normal file
9
tanstack/src/routes/posts.index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/posts/')({
|
||||
component: PostsIndexComponent,
|
||||
})
|
||||
|
||||
function PostsIndexComponent() {
|
||||
return <div>Select a post.</div>
|
||||
}
|
||||
38
tanstack/src/routes/posts.route.tsx
Normal file
38
tanstack/src/routes/posts.route.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||
import { fetchPosts } from '../utils/posts'
|
||||
|
||||
export const Route = createFileRoute('/posts')({
|
||||
loader: async () => fetchPosts(),
|
||||
component: PostsLayoutComponent,
|
||||
})
|
||||
|
||||
function PostsLayoutComponent() {
|
||||
const posts = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="p-2 flex gap-2">
|
||||
<ul className="list-disc pl-4">
|
||||
{[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
|
||||
(post) => {
|
||||
return (
|
||||
<li key={post.id} className="whitespace-nowrap">
|
||||
<Link
|
||||
to="/posts/$postId"
|
||||
params={{
|
||||
postId: post.id,
|
||||
}}
|
||||
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||
activeProps={{ className: 'text-black font-bold' }}
|
||||
>
|
||||
<div>{post.title.substring(0, 20)}</div>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</ul>
|
||||
<hr />
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
29
tanstack/src/routes/posts_.$postId.deep.tsx
Normal file
29
tanstack/src/routes/posts_.$postId.deep.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router'
|
||||
import { fetchPost } from '../utils/posts'
|
||||
import { PostErrorComponent } from '~/components/PostError'
|
||||
|
||||
export const Route = createFileRoute('/posts_/$postId/deep')({
|
||||
loader: async ({ params: { postId } }) =>
|
||||
fetchPost({
|
||||
data: postId,
|
||||
}),
|
||||
errorComponent: PostErrorComponent,
|
||||
component: PostDeepComponent,
|
||||
})
|
||||
|
||||
function PostDeepComponent() {
|
||||
const post = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="p-2 space-y-2">
|
||||
<Link
|
||||
to="/posts"
|
||||
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||
>
|
||||
← All Posts
|
||||
</Link>
|
||||
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
||||
<div className="text-sm">{post.body}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
tanstack/src/routes/redirect.tsx
Normal file
9
tanstack/src/routes/redirect.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/redirect')({
|
||||
beforeLoad: async () => {
|
||||
throw redirect({
|
||||
to: '/posts',
|
||||
})
|
||||
},
|
||||
})
|
||||
33
tanstack/src/routes/users.$userId.tsx
Normal file
33
tanstack/src/routes/users.$userId.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import axios from 'redaxios'
|
||||
import type { User } from '~/utils/users'
|
||||
import { DEPLOY_URL } from '~/utils/users'
|
||||
import { NotFound } from '~/components/NotFound'
|
||||
import { UserErrorComponent } from '~/components/UserError'
|
||||
|
||||
export const Route = createFileRoute('/users/$userId')({
|
||||
loader: async ({ params: { userId } }) => {
|
||||
return await axios
|
||||
.get<User>(DEPLOY_URL + '/api/users/' + userId)
|
||||
.then((r) => r.data)
|
||||
.catch(() => {
|
||||
throw new Error('Failed to fetch user')
|
||||
})
|
||||
},
|
||||
errorComponent: UserErrorComponent,
|
||||
component: UserComponent,
|
||||
notFoundComponent: () => {
|
||||
return <NotFound>User not found</NotFound>
|
||||
},
|
||||
})
|
||||
|
||||
function UserComponent() {
|
||||
const user = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-bold underline">{user.name}</h4>
|
||||
<div className="text-sm">{user.email}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
tanstack/src/routes/users.index.tsx
Normal file
9
tanstack/src/routes/users.index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/users/')({
|
||||
component: UsersIndexComponent,
|
||||
})
|
||||
|
||||
function UsersIndexComponent() {
|
||||
return <div>Select a user.</div>
|
||||
}
|
||||
48
tanstack/src/routes/users.route.tsx
Normal file
48
tanstack/src/routes/users.route.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||
import axios from 'redaxios'
|
||||
import { DEPLOY_URL } from '../utils/users'
|
||||
import type { User } from '../utils/users'
|
||||
|
||||
export const Route = createFileRoute('/users')({
|
||||
loader: async () => {
|
||||
return await axios
|
||||
.get<Array<User>>(DEPLOY_URL + '/api/users')
|
||||
.then((r) => r.data)
|
||||
.catch(() => {
|
||||
throw new Error('Failed to fetch users')
|
||||
})
|
||||
},
|
||||
component: UsersLayoutComponent,
|
||||
})
|
||||
|
||||
function UsersLayoutComponent() {
|
||||
const users = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="p-2 flex gap-2">
|
||||
<ul className="list-disc pl-4">
|
||||
{[
|
||||
...users,
|
||||
{ id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
|
||||
].map((user) => {
|
||||
return (
|
||||
<li key={user.id} className="whitespace-nowrap">
|
||||
<Link
|
||||
to="/users/$userId"
|
||||
params={{
|
||||
userId: String(user.id),
|
||||
}}
|
||||
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||
activeProps={{ className: 'text-black font-bold' }}
|
||||
>
|
||||
<div>{user.name}</div>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<hr />
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user