-
-
Notifications
You must be signed in to change notification settings - Fork 574
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5d49120
commit 14160f5
Showing
8 changed files
with
356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
.DS_Store | ||
dist | ||
dist-ssr | ||
*.local |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Example | ||
|
||
To run this example: | ||
|
||
- `npm install` or `yarn` | ||
- `npm start` or `yarn start` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite App</title> | ||
<script src="https://cdn.tailwindcss.com"></script> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "tanstack-router-react-example-basic", | ||
"version": "0.0.0", | ||
"private": true, | ||
"scripts": { | ||
"dev": "vite --port=3000", | ||
"build": "vite build", | ||
"serve": "vite preview", | ||
"start": "vite" | ||
}, | ||
"dependencies": { | ||
"@tanstack/react-actions": "0.0.1-beta.113", | ||
"@tanstack/react-loaders": "0.0.1-beta.113", | ||
"@tanstack/router": "0.0.1-beta.117", | ||
"@tanstack/router-devtools": "0.0.1-beta.117", | ||
"@vitejs/plugin-react": "^1.1.3", | ||
"axios": "^1.1.3", | ||
"framer-motion": "^10.13.0", | ||
"immer": "^9.0.15", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"vite": "^2.8.6", | ||
"zod": "^3.19.1" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.25", | ||
"@types/react-dom": "^18.0.8" | ||
}, | ||
"pnpm": { | ||
"overrides": { | ||
"@tanstack/router": "workspace:*", | ||
"@tanstack/react-actions": "workspace:*", | ||
"@tanstack/react-loaders": "workspace:*", | ||
"@tanstack/router-devtools": "workspace:*" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
import React from 'react' | ||
import ReactDOM from 'react-dom/client' | ||
import { AnimatePresence, motion } from 'framer-motion' | ||
import { | ||
Outlet, | ||
RouterProvider, | ||
Router, | ||
Link, | ||
RootRoute, | ||
Route, | ||
ErrorComponent, | ||
useRouter, | ||
useMatches, | ||
RouterContext, | ||
} from '@tanstack/router' | ||
import { TanStackRouterDevtools } from '@tanstack/router-devtools' | ||
import axios from 'axios' | ||
import { | ||
LoaderClient, | ||
Loader, | ||
LoaderClientProvider, | ||
useLoader, | ||
} from '@tanstack/react-loaders' | ||
|
||
type PostType = { | ||
id: string | ||
title: string | ||
body: string | ||
} | ||
|
||
const fetchPosts = async () => { | ||
console.log('Fetching posts...') | ||
await new Promise((r) => setTimeout(r, 500)) | ||
return axios | ||
.get<PostType[]>('https://jsonplaceholder.typicode.com/posts') | ||
.then((r) => r.data.slice(0, 10)) | ||
} | ||
|
||
const fetchPost = async (postId: string) => { | ||
console.log(`Fetching post with id ${postId}...`) | ||
await new Promise((r) => setTimeout(r, 500)) | ||
const post = await axios | ||
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`) | ||
.then((r) => r.data) | ||
|
||
if (!post) { | ||
throw new NotFoundError(`Post with id "${postId}" not found!`) | ||
} | ||
|
||
return post | ||
} | ||
|
||
const postsLoader = new Loader({ | ||
fn: fetchPosts, | ||
}) | ||
|
||
const postLoader = new Loader({ | ||
fn: fetchPost, | ||
onInvalidate: () => { | ||
postsLoader.invalidate() | ||
}, | ||
}) | ||
|
||
const loaderClient = new LoaderClient({ | ||
getLoaders: () => ({ | ||
posts: postsLoader, | ||
post: postLoader, | ||
}), | ||
}) | ||
|
||
declare module '@tanstack/react-loaders' { | ||
interface Register { | ||
loaderClient: typeof loaderClient | ||
} | ||
} | ||
|
||
type RouterContext = { | ||
loaderClient: typeof loaderClient | ||
} | ||
|
||
export const transitionProps = { | ||
initial: { y: -20, opacity: 0, position: 'absolute' }, | ||
animate: { y: 0, opacity: 1, damping: 5 }, | ||
exit: { y: 60, opacity: 0 }, | ||
transition: { | ||
type: 'spring', | ||
stiffness: 150, | ||
damping: 10, | ||
}, | ||
} as const | ||
|
||
const rootRoute = RootRoute.withRouterContext<RouterContext>()({ | ||
component: () => { | ||
const router = useRouter() | ||
const matches = useMatches() | ||
return ( | ||
<> | ||
<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> | ||
</div> | ||
<hr /> | ||
<AnimatePresence mode="wait"> | ||
<RouterContext router={router} key={router.state.location.key}> | ||
<Outlet /> | ||
</RouterContext> | ||
</AnimatePresence> | ||
{/* Start rendering router matches */} | ||
<TanStackRouterDevtools position="bottom-right" /> | ||
</> | ||
) | ||
}, | ||
}) | ||
|
||
const indexRoute = new Route({ | ||
getParentRoute: () => rootRoute, | ||
path: '/', | ||
component: () => { | ||
return ( | ||
<motion.div className="p-2" {...transitionProps}> | ||
<h3>Welcome Home!</h3> | ||
</motion.div> | ||
) | ||
}, | ||
}) | ||
|
||
const postsRoute = new Route({ | ||
getParentRoute: () => rootRoute, | ||
path: 'posts', | ||
loader: async ({ context }) => { | ||
const postsLoader = context.loaderClient.loaders.posts | ||
|
||
await postsLoader.load() | ||
|
||
return { | ||
promise: new Promise((r) => setTimeout(r, 500)), | ||
useLoader: () => | ||
useLoader({ | ||
loader: postsLoader, | ||
}), | ||
} | ||
}, | ||
component: ({ useLoader }) => { | ||
const postsLoader = useLoader().useLoader() | ||
|
||
return ( | ||
<motion.div className="p-2 flex gap-2" {...transitionProps}> | ||
<ul className="list-disc pl-4"> | ||
{[ | ||
...postsLoader.state.data, | ||
{ id: 'i-do-not-exist', title: 'Non-existent Post' }, | ||
]?.map((post) => { | ||
return ( | ||
<li key={post.id} className="whitespace-nowrap"> | ||
<Link | ||
to={postRoute.to} | ||
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 /> | ||
</motion.div> | ||
) | ||
}, | ||
}) | ||
|
||
const postsIndexRoute = new Route({ | ||
getParentRoute: () => postsRoute, | ||
path: '/', | ||
component: () => <div>Select a post.</div>, | ||
}) | ||
|
||
class NotFoundError extends Error {} | ||
|
||
const postRoute = new Route({ | ||
getParentRoute: () => postsRoute, | ||
path: '$postId', | ||
loader: async ({ context: { loaderClient }, params: { postId } }) => { | ||
const postLoader = loaderClient.loaders.post | ||
await postLoader.load({ | ||
variables: postId, | ||
}) | ||
|
||
// Return a curried hook! | ||
return () => | ||
useLoader({ | ||
loader: postLoader, | ||
variables: postId, | ||
}) | ||
}, | ||
errorComponent: ({ error }) => { | ||
if (error instanceof NotFoundError) { | ||
return <div>{error.message}</div> | ||
} | ||
|
||
return <ErrorComponent error={error} /> | ||
}, | ||
component: () => { | ||
const { | ||
state: { data: post }, | ||
} = postRoute.useLoader()() | ||
|
||
return ( | ||
<div className="space-y-2"> | ||
<h4 className="text-xl font-bold underline">{post.title}</h4> | ||
<div className="text-sm">{post.body}</div> | ||
</div> | ||
) | ||
}, | ||
}) | ||
|
||
const routeTree = rootRoute.addChildren([ | ||
postsRoute.addChildren([postRoute, postsIndexRoute]), | ||
indexRoute, | ||
]) | ||
|
||
// Set up a Router instance | ||
const router = new Router({ | ||
routeTree, | ||
defaultPreload: 'intent', | ||
context: { | ||
loaderClient, | ||
}, | ||
}) | ||
|
||
// Register things for typesafety | ||
declare module '@tanstack/router' { | ||
interface Register { | ||
router: typeof router | ||
} | ||
} | ||
|
||
const rootElement = document.getElementById('app')! | ||
|
||
if (!rootElement.innerHTML) { | ||
const root = ReactDOM.createRoot(rootElement) | ||
|
||
root.render( | ||
// <React.StrictMode> | ||
<LoaderClientProvider loaderClient={loaderClient}> | ||
<RouterProvider router={router} /> | ||
</LoaderClientProvider>, | ||
// </React.StrictMode>, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"composite": true, | ||
"extends": "../../../tsconfig.base.json", | ||
"compilerOptions": { | ||
"outDir": "./build/types" | ||
}, | ||
"files": ["src/main.tsx"], | ||
"include": [ | ||
"src" | ||
// "__tests__/**/*.test.*" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"compilerOptions": { | ||
"strict": true, | ||
"esModuleInterop": true, | ||
"jsx": "react" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { defineConfig } from 'vite' | ||
import react from '@vitejs/plugin-react' | ||
|
||
// https://vitejs.dev/config/ | ||
export default defineConfig({ | ||
plugins: [react()], | ||
}) |