Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/solid/authenticated-routes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
11 changes: 11 additions & 0 deletions examples/solid/authenticated-routes/.vscode/settings.json
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
}
}
6 changes: 6 additions & 0 deletions examples/solid/authenticated-routes/README.md
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`
12 changes: 12 additions & 0 deletions examples/solid/authenticated-routes/index.html
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>
27 changes: 27 additions & 0 deletions examples/solid/authenticated-routes/package.json
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",
Comment on lines +13 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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:

-    "@tanstack/solid-router": "^1.135.2",
-    "@tanstack/solid-router-devtools": "^1.135.2",
-    "@tanstack/router-plugin": "^1.135.2",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "workspace:*",
+    "@tanstack/router-plugin": "workspace:*",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@tanstack/solid-router": "^1.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/router-plugin": "^1.135.2",
"@tanstack/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
"@tanstack/router-plugin": "workspace:*",
🤖 Prompt for AI Agents
In examples/solid/authenticated-routes/package.json around lines 13 to 15 the
internal TanStack dependencies use version ranges; replace the version ranges
for "@tanstack/solid-router", "@tanstack/solid-router-devtools", and
"@tanstack/router-plugin" with the workspace:* protocol so they read as
workspace:* to ensure monorepo dependency resolution per guidelines.

"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"
}
}
5 changes: 5 additions & 0 deletions examples/solid/authenticated-routes/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
63 changes: 63 additions & 0 deletions examples/solid/authenticated-routes/src/auth.tsx
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In examples/solid/authenticated-routes/src/auth.tsx around lines 46 to 48, the
Solid.createEffect currently reads localStorage on every render; remove this
effect or replace it with a single-run mount handler that either (a) removes the
Solid.createEffect entirely since initial state is already set from storage on
line 29, or (b) implements proper cross-tab sync by adding a
window.addEventListener('storage', ...) on mount that checks the auth key, calls
setUser(getStoredUser()) when that key changes, and removes the listener on
cleanup so it only runs in response to storage events rather than every render.


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
}
40 changes: 40 additions & 0 deletions examples/solid/authenticated-routes/src/main.tsx
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)
50 changes: 50 additions & 0 deletions examples/solid/authenticated-routes/src/posts.tsx
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the loaderDelayFn parameter signature.

The function signature declares fn: (...args: Array<any>) suggesting it accepts arguments, but Line 8 calls fn() without passing any arguments. This is misleading and could cause issues if a function expecting parameters is passed in.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
}
async function loaderDelayFn<T>(fn: () => 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
}
🤖 Prompt for AI Agents
In examples/solid/authenticated-routes/src/posts.tsx around lines 3 to 11, the
loaderDelayFn parameter is typed as fn: (...args: Array<any>) which implies it
expects arguments but the implementation always calls fn() with no args; change
the parameter signature to fn: () => Promise<T> | T so it correctly represents a
no-argument callback (update the type only, leaving the call site as fn()).


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
}),
)
}
Loading
Loading