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
4 changes: 4 additions & 0 deletions docs/router/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,10 @@
"label": "Authenticated Routes",
"to": "framework/solid/examples/authenticated-routes"
},
{
"label": "Scroll Restoration",
"to": "framework/solid/examples/scroll-restoration"
},
{
"label": "Deferred Data",
"to": "framework/solid/examples/deferred-data"
Expand Down
5 changes: 5 additions & 0 deletions examples/solid/scroll-restoration/.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/scroll-restoration/.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/scroll-restoration/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/scroll-restoration/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>
25 changes: 25 additions & 0 deletions examples/solid/scroll-restoration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "tanstack-router-solid-example-scroll-restoration",
"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",
Comment on lines +13 to +14
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 dependencies.

The internal TanStack Router packages should use the workspace:* protocol instead of version numbers to ensure they reference the local workspace packages during development.

As per coding guidelines.

Apply this diff:

-    "@tanstack/solid-router": "^1.135.2",
-    "@tanstack/solid-router-devtools": "^1.135.2",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "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/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
🤖 Prompt for AI Agents
In examples/solid/scroll-restoration/package.json around lines 13 to 14, the
internal TanStack Router dependencies currently use fixed version numbers;
change both "@tanstack/solid-router" and "@tanstack/solid-router-devtools" to
use the workspace protocol by replacing their version strings with "workspace:*"
so they resolve to the local workspace packages during development.

"@tanstack/solid-virtual": "^3.13.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
"tailwindcss": "^4.1.15"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
}
5 changes: 5 additions & 0 deletions examples/solid/scroll-restoration/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
196 changes: 196 additions & 0 deletions examples/solid/scroll-restoration/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { render } from 'solid-js/web'
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
useElementScrollRestoration,
} from '@tanstack/solid-router'
import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
import { createVirtualizer } from '@tanstack/solid-virtual'
import './styles.css'

const rootRoute = createRootRoute({
component: RootComponent,
})

function RootComponent() {
return (
<>
<div class="p-2 flex gap-2 sticky top-0 border-b bg-gray-100 dark:bg-gray-900">
<Link to="/" class="[&.active]:font-bold">
Home
</Link>{' '}
<Link to="/about" class="[&.active]:font-bold">
About
</Link>
<Link to="/about" resetScroll={false}>
About (No Reset)
</Link>
<Link to="/by-element" class="[&.active]:font-bold">
By-Element
</Link>
</div>
<Outlet />
<TanStackRouterDevtools />
</>
)
}

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
loader: () => new Promise<void>((r) => setTimeout(r, 500)),
component: IndexComponent,
})

function IndexComponent() {
return (
<div class="p-2">
<h3>Welcome Home!</h3>
<div class="space-y-2">
{Array.from({ length: 50 }).map((_, i) => (
<div class="h-[100px] p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border">
Home Item {i + 1}
</div>
))}
</div>
</div>
)
}

const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
loader: () => new Promise<void>((r) => setTimeout(r, 500)),
component: AboutComponent,
})

function AboutComponent() {
return (
<div class="p-2">
<div>Hello from About!</div>
<div class="space-y-2">
{Array.from({ length: 50 }).map((_, i) => (
<div class="h-[100px] p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border">
About Item {i + 1}
</div>
))}
</div>
</div>
)
}

const byElementRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/by-element',
loader: () => new Promise<void>((r) => setTimeout(r, 500)),
component: ByElementComponent,
})

function ByElementComponent() {
// We need a unique ID for manual scroll restoration on a specific element
// It should be as unique as possible for this element across your app
const scrollRestorationId = 'myVirtualizedContent'

// We use that ID to get the scroll entry for this element
const scrollEntry = useElementScrollRestoration({
id: scrollRestorationId,
})

// Let's use TanStack Virtual to virtualize some content!
let virtualizerParentRef: any
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid any type for better type safety.

The any type weakens TypeScript's type checking. Use a more specific type for the virtualizer parent ref.

As per coding guidelines.

Apply this diff:

-  let virtualizerParentRef: any
+  let virtualizerParentRef: HTMLDivElement | undefined
📝 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
let virtualizerParentRef: any
let virtualizerParentRef: HTMLDivElement | undefined
🤖 Prompt for AI Agents
In examples/solid/scroll-restoration/src/main.tsx around line 104, the variable
virtualizerParentRef is typed as any which weakens type safety; change its type
to a concrete DOM/ref type such as HTMLDivElement | null (or a Solid ref type
like Ref<HTMLDivElement | null> / (el: HTMLDivElement | null) => void if using
function refs), update any related assignments/uses to match the chosen type,
and add necessary imports or type annotations so TypeScript enforces correct DOM
element handling.

const virtualizer = createVirtualizer({
count: 10000,
getScrollElement: () => virtualizerParentRef,
estimateSize: () => 100,
// We pass the scrollY from the scroll restoration entry to the virtualizer
// as the initial offset
initialOffset: scrollEntry?.scrollY,
})

return (
<div class="p-2 h-[calc(100vh-41px)] flex flex-col">
<div>Hello from By-Element!</div>
<div class="h-full min-h-0 flex gap-4">
<div class="border rounded-lg p-2 overflow-auto flex-1 space-y-2">
{Array.from({ length: 50 }).map((_, i) => (
<div class="h-[100px] p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border">
About Item {i + 1}
</div>
))}
</div>
<div class="flex-1 overflow-auto flex flex-col gap-4">
{Array.from({ length: 2 }).map((_, i) => (
<div class="flex-1 border rounded-lg p-2 overflow-auto">
<div class="space-y-2">
{Array.from({ length: 50 }).map((_, i) => (
<div class="h-[100px] p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border">
About Item {i + 1}
</div>
))}
</div>
</div>
))}
<div class="flex-1 flex flex-col min-h-0">
<div class="font-bold">Virtualized</div>
<div
ref={virtualizerParentRef}
// We pass the scroll restoration ID to the element
// as a custom attribute that will get picked up by the
// scroll restoration watcher
data-scroll-restoration-id={scrollRestorationId}
class="flex-1 border rounded-lg overflow-auto relative"
>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
}}
>
{virtualizer.getVirtualItems().map((item) => (
<div
class="absolute p-2 pb-0 w-full"
style={{
height: item.size + 'px',
top: item.start + 'px',
}}
>
<div class="p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border h-full">
Virtualized Item {item.index + 1}
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
)
}

const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
byElementRoute,
])

const router = createRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
})

declare module '@tanstack/solid-router' {
interface Register {
router: typeof router
}
}

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
render(() => <RouterProvider router={router} />, rootElement)
}
21 changes: 21 additions & 0 deletions examples/solid/scroll-restoration/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import 'tailwindcss';

@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}

html {
color-scheme: light dark;
}
* {
@apply border-gray-200 dark:border-gray-800;
}
body {
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
}
12 changes: 12 additions & 0 deletions examples/solid/scroll-restoration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"target": "ESNext",
"moduleResolution": "Bundler",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"skipLibCheck": true
}
}
7 changes: 7 additions & 0 deletions examples/solid/scroll-restoration/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [solid()],
})
34 changes: 34 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading