-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
docs(solid-router): authenticated-routes example #5825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| node_modules | ||
| .DS_Store | ||
| dist | ||
| dist-ssr | ||
| *.local |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "files.watcherExclude": { | ||
| "**/routeTree.gen.ts": true | ||
| }, | ||
| "search.exclude": { | ||
| "**/routeTree.gen.ts": true | ||
| }, | ||
| "files.readonlyInclude": { | ||
| "**/routeTree.gen.ts": true | ||
| } | ||
| } |
| 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` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <!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> | ||
| </head> | ||
| <body> | ||
| <div id="app"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "tanstack-router-solid-example-authenticated-routes", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 3000", | ||
| "build": "vite build && tsc --noEmit", | ||
| "serve": "vite preview", | ||
| "start": "vite" | ||
| }, | ||
| "dependencies": { | ||
| "@tailwindcss/postcss": "^4.1.15", | ||
| "@tanstack/solid-router": "^1.135.2", | ||
| "@tanstack/solid-router-devtools": "^1.135.2", | ||
| "@tanstack/router-plugin": "^1.135.2", | ||
| "postcss": "^8.5.1", | ||
| "solid-js": "^1.9.10", | ||
| "redaxios": "^0.5.1", | ||
| "tailwindcss": "^4.1.15", | ||
| "zod": "^3.24.2" | ||
| }, | ||
| "devDependencies": { | ||
| "vite-plugin-solid": "^2.11.10", | ||
| "typescript": "^5.7.2", | ||
| "vite": "^7.1.7" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export default { | ||
| plugins: { | ||
| '@tailwindcss/postcss': {}, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import * as Solid from 'solid-js' | ||
|
|
||
| import { sleep } from './utils' | ||
|
|
||
| export interface AuthContext { | ||
| isAuthenticated: () => boolean | ||
| login: (username: string) => Promise<void> | ||
| logout: () => Promise<void> | ||
| user: () => string | null | ||
| } | ||
|
|
||
| const AuthContext = Solid.createContext<AuthContext | null>(null) | ||
|
|
||
| const key = 'tanstack.auth.user' | ||
|
|
||
| function getStoredUser() { | ||
| return localStorage.getItem(key) | ||
| } | ||
|
|
||
| function setStoredUser(user: string | null) { | ||
| if (user) { | ||
| localStorage.setItem(key, user) | ||
| } else { | ||
| localStorage.removeItem(key) | ||
| } | ||
| } | ||
|
|
||
| export function AuthProvider(props: { children: Solid.JSX.Element }) { | ||
| const [user, setUser] = Solid.createSignal<string | null>(getStoredUser()) | ||
| const isAuthenticated = () => !!user() | ||
|
|
||
| const logout = async () => { | ||
| await sleep(250) | ||
|
|
||
| setStoredUser(null) | ||
| setUser(null) | ||
| } | ||
|
|
||
| const login = async (username: string) => { | ||
| await sleep(500) | ||
|
|
||
| setStoredUser(username) | ||
| setUser(username) | ||
| } | ||
|
|
||
| Solid.createEffect(() => { | ||
| setUser(getStoredUser()) | ||
| }) | ||
|
Comment on lines
+46
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove or properly implement the createEffect for storage synchronization. This effect runs on every render cycle and redundantly reads from localStorage without any dependencies. The initial user state is already correctly initialized from storage on line 29. If the intent is to synchronize authentication state across browser tabs, implement a proper storage event listener instead: - Solid.createEffect(() => {
- setUser(getStoredUser())
- })
+ Solid.onMount(() => {
+ const handleStorageChange = (e: StorageEvent) => {
+ if (e.key === key) {
+ setUser(e.newValue)
+ }
+ }
+ window.addEventListener('storage', handleStorageChange)
+ Solid.onCleanup(() => window.removeEventListener('storage', handleStorageChange))
+ })Alternatively, if cross-tab sync is not needed, simply remove the effect.
🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <AuthContext.Provider value={{ isAuthenticated, user, login, logout }}> | ||
| {props.children} | ||
| </AuthContext.Provider> | ||
| ) | ||
| } | ||
|
|
||
| export function useAuth() { | ||
| const context = Solid.useContext(AuthContext) | ||
| if (!context) { | ||
| throw new Error('useAuth must be used within an AuthProvider') | ||
| } | ||
| return context | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { render } from 'solid-js/web' | ||
| import { RouterProvider, createRouter } from '@tanstack/solid-router' | ||
|
|
||
| import { routeTree } from './routeTree.gen' | ||
| import { AuthProvider, useAuth } from './auth' | ||
| import './styles.css' | ||
|
|
||
| // Set up a Router instance | ||
| const router = createRouter({ | ||
| routeTree, | ||
| defaultPreload: 'intent', | ||
| scrollRestoration: true, | ||
| context: { | ||
| auth: undefined!, // This will be set after we wrap the app in an AuthProvider | ||
| }, | ||
| }) | ||
|
|
||
| // Register things for typesafety | ||
| declare module '@tanstack/solid-router' { | ||
| interface Register { | ||
| router: typeof router | ||
| } | ||
| } | ||
|
|
||
| function InnerApp() { | ||
| const auth = useAuth() | ||
| return <RouterProvider router={router} context={{ auth }} /> | ||
| } | ||
|
|
||
| function App() { | ||
| return ( | ||
| <AuthProvider> | ||
| <InnerApp /> | ||
| </AuthProvider> | ||
| ) | ||
| } | ||
|
|
||
| const rootElement = document.getElementById('app')! | ||
|
|
||
| render(() => <App />, rootElement) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||||||||||||||||||||||
| import axios from 'redaxios' | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| async function loaderDelayFn<T>(fn: (...args: Array<any>) => Promise<T> | T) { | ||||||||||||||||||||||||||||||||||||||
| const delay = Number(sessionStorage.getItem('loaderDelay') ?? 0) | ||||||||||||||||||||||||||||||||||||||
| const delayPromise = new Promise((r) => setTimeout(r, delay)) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| await delayPromise | ||||||||||||||||||||||||||||||||||||||
| const res = await fn() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return res | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the The function signature declares Apply this diff to fix the signature: -async function loaderDelayFn<T>(fn: (...args: Array<any>) => Promise<T> | T) {
+async function loaderDelayFn<T>(fn: () => Promise<T> | T) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| type Invoice = { | ||||||||||||||||||||||||||||||||||||||
| id: number | ||||||||||||||||||||||||||||||||||||||
| title: string | ||||||||||||||||||||||||||||||||||||||
| body: string | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let invoices: Array<Invoice> = null! | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let invoicesPromise: Promise<void> | undefined = undefined | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const ensureInvoices = async () => { | ||||||||||||||||||||||||||||||||||||||
| if (!invoicesPromise) { | ||||||||||||||||||||||||||||||||||||||
| invoicesPromise = Promise.resolve().then(async () => { | ||||||||||||||||||||||||||||||||||||||
| const { data } = await axios.get( | ||||||||||||||||||||||||||||||||||||||
| 'https://jsonplaceholder.typicode.com/posts', | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| invoices = data.slice(0, 10) | ||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| await invoicesPromise | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export async function fetchInvoices() { | ||||||||||||||||||||||||||||||||||||||
| return loaderDelayFn(() => ensureInvoices().then(() => invoices)) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export async function fetchInvoiceById(id: number) { | ||||||||||||||||||||||||||||||||||||||
| return loaderDelayFn(() => | ||||||||||||||||||||||||||||||||||||||
| ensureInvoices().then(() => { | ||||||||||||||||||||||||||||||||||||||
| const invoice = invoices.find((d) => d.id === id) | ||||||||||||||||||||||||||||||||||||||
| if (!invoice) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('Invoice not found') | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return invoice | ||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
workspace:*protocol for internal TanStack dependencies.Internal monorepo dependencies should use the
workspace:*protocol instead of version ranges to ensure proper monorepo dependency resolution.As per coding guidelines.
Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents