-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: match loading #7637
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
base: main
Are you sure you want to change the base?
fix: match loading #7637
Changes from all commits
eeedf96
22e132f
abacc31
d71b6f5
14895fb
22bf4af
7b7d52e
c928d31
b9897a4
f7be0ae
72ddee9
1d394e1
0d36b6c
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,7 @@ | ||
| --- | ||
| '@tanstack/router-core': patch | ||
| --- | ||
|
|
||
| Fix context values from a parent route's `beforeLoad` not being propagated to sub-routes in several code paths: while a sub-route's loader reloads in the background, when re-entering a route whose background reload is still in flight, and in a sub-route's error state when its `beforeLoad` throws (the merged context is now committed together with the error status for the errorComponent to consume). | ||
|
|
||
| Redirects no longer use a renderable `RouteMatch.status`; `RouteMatch.status` is now `'pending' | 'success' | 'error' | 'notFound'`. Abandoned pending, redirected, or failed matches are dropped from cache and their pending promises are settled so stale suspense work cannot keep rendering suspended. |
| 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>Issue 7120</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,26 @@ | ||
| { | ||
| "name": "tanstack-router-e2e-react-issue-7120", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 3000", | ||
| "dev:e2e": "vite", | ||
| "build": "vite build && tsc --noEmit", | ||
| "preview": "vite preview", | ||
| "start": "vite", | ||
| "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" | ||
| }, | ||
| "dependencies": { | ||
| "@tanstack/react-router": "workspace:^", | ||
| "react": "^19.0.0", | ||
| "react-dom": "^19.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@playwright/test": "^1.50.1", | ||
| "@tanstack/router-e2e-utils": "workspace:^", | ||
| "@types/react": "^19.0.8", | ||
| "@types/react-dom": "^19.0.3", | ||
| "@vitejs/plugin-react": "^6.0.1", | ||
| "vite": "^8.0.14" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { defineConfig, devices } from '@playwright/test' | ||
| import { getTestServerPort } from '@tanstack/router-e2e-utils' | ||
| import packageJson from './package.json' with { type: 'json' } | ||
|
|
||
| const PORT = await getTestServerPort(packageJson.name) | ||
| const baseURL = `http://localhost:${PORT}` | ||
|
|
||
| export default defineConfig({ | ||
| testDir: './tests', | ||
| workers: 1, | ||
| reporter: [['line']], | ||
| use: { | ||
| baseURL, | ||
| }, | ||
| webServer: { | ||
| command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} pnpm build && pnpm preview --port ${PORT}`, | ||
| url: baseURL, | ||
| reuseExistingServer: !process.env.CI, | ||
| stdout: 'pipe', | ||
| }, | ||
| projects: [ | ||
| { | ||
| name: 'chromium', | ||
| use: { ...devices['Desktop Chrome'] }, | ||
| }, | ||
| ], | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import ReactDOM from 'react-dom/client' | ||
| import { | ||
| Outlet, | ||
| RouterProvider, | ||
| createRootRoute, | ||
| createRoute, | ||
| createRouter, | ||
| redirect, | ||
| } from '@tanstack/react-router' | ||
|
|
||
| const posts = [ | ||
| { | ||
| id: '1', | ||
| title: 'sunt aut facere repellat provident occaecati', | ||
| }, | ||
| ] | ||
|
|
||
| const rootRoute = createRootRoute({ | ||
| component: RootComponent, | ||
| pendingMs: 0, | ||
| pendingComponent: () => { | ||
| ;(globalThis as any).__pendingSeen = true | ||
| return <div data-testid="root-pending">loading</div> | ||
| }, | ||
| beforeLoad: async ({ matches }) => { | ||
| if (matches.find((match) => match.routeId === '/posts')) { | ||
| return | ||
| } | ||
|
|
||
| await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
| throw redirect({ to: '/posts' }) | ||
| }, | ||
| }) | ||
|
|
||
| function RootComponent() { | ||
| return <Outlet /> | ||
| } | ||
|
|
||
| const indexRoute = createRoute({ | ||
| getParentRoute: () => rootRoute, | ||
| path: '/', | ||
| component: () => <div>Home</div>, | ||
| }) | ||
|
|
||
| const postsRoute = createRoute({ | ||
| getParentRoute: () => rootRoute, | ||
| path: 'posts', | ||
| loader: async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 10)) | ||
| return posts | ||
| }, | ||
| }).lazy(() => import('./posts.lazy').then((d) => d.Route)) | ||
|
|
||
| const routeTree = rootRoute.addChildren([indexRoute, postsRoute]) | ||
|
|
||
| const router = createRouter({ | ||
| routeTree, | ||
| defaultViewTransition: true, | ||
| }) | ||
|
|
||
| declare module '@tanstack/react-router' { | ||
| interface Register { | ||
| router: typeof router | ||
| } | ||
| } | ||
|
|
||
| const rootElement = document.getElementById('app')! | ||
|
|
||
| if (!rootElement.innerHTML) { | ||
| const root = ReactDOM.createRoot(rootElement) | ||
| root.render(<RouterProvider router={router} />) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Link, createLazyRoute } from '@tanstack/react-router' | ||
|
|
||
| export const Route = createLazyRoute('/posts')({ | ||
| component: PostsComponent, | ||
| }) | ||
|
|
||
| function PostsComponent() { | ||
| const posts = Route.useLoaderData() | ||
|
|
||
| return ( | ||
| <div> | ||
| <ul> | ||
| {posts.map((post) => { | ||
| return ( | ||
| <li key={post.id}> | ||
| <Link to="/posts"> | ||
| <div>{post.title.substring(0, 20)}</div> | ||
| </Link> | ||
| </li> | ||
| ) | ||
| })} | ||
| </ul> | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { expect, test } from '@playwright/test' | ||
|
|
||
| test('root beforeLoad redirect does not blank when pending UI and view transitions are enabled (https://github.com/TanStack/router/issues/7120)', async ({ | ||
| page, | ||
| }) => { | ||
| const pageErrors: Array<string> = [] | ||
| const consoleErrors: Array<string> = [] | ||
|
|
||
| page.on('pageerror', (error) => { | ||
| pageErrors.push(error.message) | ||
| }) | ||
| page.on('console', (message) => { | ||
| if (message.type() === 'error') { | ||
| consoleErrors.push(message.text()) | ||
| } | ||
| }) | ||
|
|
||
| await page.goto('/') | ||
|
|
||
| await expect(page).toHaveURL(/\/posts$/) | ||
| await expect(page.getByText('sunt aut facere repe')).toBeVisible() | ||
| await expect | ||
| .poll(() => page.evaluate(() => (globalThis as any).__pendingSeen)) | ||
| .toBe(true) | ||
| await expect(page.getByTestId('root-pending')).not.toBeVisible() | ||
| expect(pageErrors).toEqual([]) | ||
| expect(consoleErrors).toEqual([]) | ||
| }) |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| { | ||||||
| "compilerOptions": { | ||||||
| "strict": true, | ||||||
| "esModuleInterop": true, | ||||||
| "jsx": "react-jsx", | ||||||
| "target": "ESNext", | ||||||
| "moduleResolution": "Bundler", | ||||||
| "module": "ESNext", | ||||||
| "resolveJsonModule": true, | ||||||
| "allowJs": true, | ||||||
| "skipLibCheck": true, | ||||||
| "types": ["vite/client"] | ||||||
|
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. Include Node types for Playwright config type-checking. Line 12 restricts global types to Suggested patch- "types": ["vite/client"]
+ "types": ["vite/client", "node"]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| }, | ||||||
| "exclude": ["node_modules", "dist"] | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { defineConfig } from 'vite' | ||
| import react from '@vitejs/plugin-react' | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [react()], | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Issue 7457</title> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </head> | ||
| <body id="root" class="flex h-svh flex-col"></body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "tanstack-router-e2e-react-issue-7457", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 3000", | ||
| "dev:e2e": "vite", | ||
| "build": "vite build && tsc --noEmit", | ||
| "preview": "vite preview", | ||
| "start": "vite", | ||
| "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" | ||
| }, | ||
| "dependencies": { | ||
| "@tanstack/react-router": "workspace:^", | ||
| "@tanstack/router-plugin": "workspace:^", | ||
|
Comment on lines
+14
to
+15
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify workspace protocol usage in this package
rg -n '"`@tanstack/`(react-router|router-plugin|router-e2e-utils)"\s*:\s*"workspace:\^"' \
e2e/react-router/issue-7457/package.jsonRepository: TanStack/router Length of output: 208 Use Lines 14–15 and 21 use Suggested patch- "`@tanstack/react-router`": "workspace:^",
- "`@tanstack/router-plugin`": "workspace:^",
+ "`@tanstack/react-router`": "workspace:*",
+ "`@tanstack/router-plugin`": "workspace:*",
@@
- "`@tanstack/router-e2e-utils`": "workspace:^",
+ "`@tanstack/router-e2e-utils`": "workspace:*",🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| "react": "^19.0.0", | ||
| "react-dom": "^19.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@playwright/test": "^1.50.1", | ||
| "@tanstack/router-e2e-utils": "workspace:^", | ||
| "@types/react": "^19.0.8", | ||
| "@types/react-dom": "^19.0.3", | ||
| "@vitejs/plugin-react": "^6.0.1", | ||
| "vite": "^8.0.14" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { defineConfig, devices } from '@playwright/test' | ||
| import { getTestServerPort } from '@tanstack/router-e2e-utils' | ||
| import packageJson from './package.json' with { type: 'json' } | ||
|
|
||
| const PORT = await getTestServerPort(packageJson.name) | ||
| const baseURL = `http://localhost:${PORT}` | ||
|
|
||
| export default defineConfig({ | ||
| testDir: './tests', | ||
| workers: 1, | ||
| reporter: [['line']], | ||
| use: { | ||
| baseURL, | ||
| }, | ||
| webServer: { | ||
| command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} pnpm build && pnpm preview --port ${PORT}`, | ||
| url: baseURL, | ||
| reuseExistingServer: !process.env.CI, | ||
| stdout: 'pipe', | ||
| }, | ||
| projects: [ | ||
| { | ||
| name: 'chromium', | ||
| use: { ...devices['Desktop Chrome'] }, | ||
| }, | ||
| ], | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { StrictMode } from 'react' | ||
| import { createRoot } from 'react-dom/client' | ||
| import { RouterProvider, createRouter } from '@tanstack/react-router' | ||
| import { routeTree } from './routeTree.gen' | ||
|
|
||
| const router = createRouter({ | ||
| routeTree, | ||
| defaultPendingComponent: DefaultPendingComponent, | ||
| defaultPreloadStaleTime: 0, | ||
| }) | ||
|
|
||
| declare module '@tanstack/react-router' { | ||
| interface Register { | ||
| router: typeof router | ||
| } | ||
| } | ||
|
|
||
| function DefaultPendingComponent() { | ||
| ;(globalThis as any).__pendingSeen = true | ||
|
|
||
| return <div data-testid="app-pending">loading</div> | ||
| } | ||
|
|
||
| createRoot(document.body).render( | ||
| <StrictMode> | ||
| <RouterProvider router={router} /> | ||
| </StrictMode>, | ||
| ) |
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.
Align internal workspace dependency ranges with repo guideline.
Line 14 and Line 20 use
workspace:^for internal packages, but this PR’s package guideline requiresworkspace:*inpackage.jsonfiles. Please switch both entries (or explicitly document an e2e exception in the guideline to avoid drift).Suggested patch
As per coding guidelines, internal dependencies in
**/package.jsonmust use the workspace protocolworkspace:*.📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines