-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
docs(solid-router): scroll restoration example #5839
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,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", | ||
| "@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" | ||
| } | ||
| } | ||
| 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,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 | ||||||
|
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. 🛠️ Refactor suggestion | 🟠 Major Avoid The As per coding guidelines. Apply this diff: - let virtualizerParentRef: any
+ let virtualizerParentRef: HTMLDivElement | undefined📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| 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) | ||||||
| } | ||||||
| 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; | ||
| } |
| 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 | ||
| } | ||
| } |
| 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()], | ||
| }) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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 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:
📝 Committable suggestion
🤖 Prompt for AI Agents