Skip to content

Commit 8f57746

Browse files
authored
docs(solid-router): authenticated-routes example (#5825)
1 parent f0e0279 commit 8f57746

23 files changed

+830
-0
lines changed
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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "tanstack-router-solid-example-authenticated-routes",
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+
"@tanstack/router-plugin": "^1.135.2",
16+
"postcss": "^8.5.1",
17+
"solid-js": "^1.9.10",
18+
"redaxios": "^0.5.1",
19+
"tailwindcss": "^4.1.15",
20+
"zod": "^3.24.2"
21+
},
22+
"devDependencies": {
23+
"vite-plugin-solid": "^2.11.10",
24+
"typescript": "^5.7.2",
25+
"vite": "^7.1.7"
26+
}
27+
}
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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as Solid from 'solid-js'
2+
3+
import { sleep } from './utils'
4+
5+
export interface AuthContext {
6+
isAuthenticated: () => boolean
7+
login: (username: string) => Promise<void>
8+
logout: () => Promise<void>
9+
user: () => string | null
10+
}
11+
12+
const AuthContext = Solid.createContext<AuthContext | null>(null)
13+
14+
const key = 'tanstack.auth.user'
15+
16+
function getStoredUser() {
17+
return localStorage.getItem(key)
18+
}
19+
20+
function setStoredUser(user: string | null) {
21+
if (user) {
22+
localStorage.setItem(key, user)
23+
} else {
24+
localStorage.removeItem(key)
25+
}
26+
}
27+
28+
export function AuthProvider(props: { children: Solid.JSX.Element }) {
29+
const [user, setUser] = Solid.createSignal<string | null>(getStoredUser())
30+
const isAuthenticated = () => !!user()
31+
32+
const logout = async () => {
33+
await sleep(250)
34+
35+
setStoredUser(null)
36+
setUser(null)
37+
}
38+
39+
const login = async (username: string) => {
40+
await sleep(500)
41+
42+
setStoredUser(username)
43+
setUser(username)
44+
}
45+
46+
Solid.createEffect(() => {
47+
setUser(getStoredUser())
48+
})
49+
50+
return (
51+
<AuthContext.Provider value={{ isAuthenticated, user, login, logout }}>
52+
{props.children}
53+
</AuthContext.Provider>
54+
)
55+
}
56+
57+
export function useAuth() {
58+
const context = Solid.useContext(AuthContext)
59+
if (!context) {
60+
throw new Error('useAuth must be used within an AuthProvider')
61+
}
62+
return context
63+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { render } from 'solid-js/web'
2+
import { RouterProvider, createRouter } from '@tanstack/solid-router'
3+
4+
import { routeTree } from './routeTree.gen'
5+
import { AuthProvider, useAuth } from './auth'
6+
import './styles.css'
7+
8+
// Set up a Router instance
9+
const router = createRouter({
10+
routeTree,
11+
defaultPreload: 'intent',
12+
scrollRestoration: true,
13+
context: {
14+
auth: undefined!, // This will be set after we wrap the app in an AuthProvider
15+
},
16+
})
17+
18+
// Register things for typesafety
19+
declare module '@tanstack/solid-router' {
20+
interface Register {
21+
router: typeof router
22+
}
23+
}
24+
25+
function InnerApp() {
26+
const auth = useAuth()
27+
return <RouterProvider router={router} context={{ auth }} />
28+
}
29+
30+
function App() {
31+
return (
32+
<AuthProvider>
33+
<InnerApp />
34+
</AuthProvider>
35+
)
36+
}
37+
38+
const rootElement = document.getElementById('app')!
39+
40+
render(() => <App />, rootElement)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import axios from 'redaxios'
2+
3+
async function loaderDelayFn<T>(fn: (...args: Array<any>) => Promise<T> | T) {
4+
const delay = Number(sessionStorage.getItem('loaderDelay') ?? 0)
5+
const delayPromise = new Promise((r) => setTimeout(r, delay))
6+
7+
await delayPromise
8+
const res = await fn()
9+
10+
return res
11+
}
12+
13+
type Invoice = {
14+
id: number
15+
title: string
16+
body: string
17+
}
18+
19+
let invoices: Array<Invoice> = null!
20+
21+
let invoicesPromise: Promise<void> | undefined = undefined
22+
23+
const ensureInvoices = async () => {
24+
if (!invoicesPromise) {
25+
invoicesPromise = Promise.resolve().then(async () => {
26+
const { data } = await axios.get(
27+
'https://jsonplaceholder.typicode.com/posts',
28+
)
29+
invoices = data.slice(0, 10)
30+
})
31+
}
32+
33+
await invoicesPromise
34+
}
35+
36+
export async function fetchInvoices() {
37+
return loaderDelayFn(() => ensureInvoices().then(() => invoices))
38+
}
39+
40+
export async function fetchInvoiceById(id: number) {
41+
return loaderDelayFn(() =>
42+
ensureInvoices().then(() => {
43+
const invoice = invoices.find((d) => d.id === id)
44+
if (!invoice) {
45+
throw new Error('Invoice not found')
46+
}
47+
return invoice
48+
}),
49+
)
50+
}

0 commit comments

Comments
 (0)