Skip to content

Commit a15a239

Browse files
committed
add Minimal e2e example to demo the head function's set title bug
Open http://localhost:3000/test-head/article/123 1. Initially not logged in. You see page title "title n/a", content "Article Not Accessible." 2. Click "Log in" (see utils/fake-auth), which invalidates the route's loader data, trigger refetch; 1 second later, you should see the article content becoming available, But, the title stays n/a. 3. Now manually reload the page, confirm that both page title and article content are available. 4. Click "Log out", which again invalidates the loader data. 1 second later, you should see the article content becoming Unavailable, But, the title is still available! 5. Reload the page, confirm that both page title and article content are Unavailable.
1 parent 5b17c13 commit a15a239

File tree

4 files changed

+113
-8
lines changed

4 files changed

+113
-8
lines changed

e2e/solid-start/basic/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
4040
import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index'
4141
import { Route as TransitionTypingCreateResourceRouteImport } from './routes/transition/typing/create-resource'
4242
import { Route as TransitionCountCreateResourceRouteImport } from './routes/transition/count/create-resource'
43+
import { Route as TestHeadArticleIdRouteImport } from './routes/test-head/article.$id'
4344
import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader'
4445
import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad'
4546
import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep'
@@ -210,6 +211,11 @@ const TransitionCountCreateResourceRoute =
210211
path: '/transition/count/create-resource',
211212
getParentRoute: () => rootRouteImport,
212213
} as any)
214+
const TestHeadArticleIdRoute = TestHeadArticleIdRouteImport.update({
215+
id: '/test-head/article/$id',
216+
path: '/test-head/article/$id',
217+
getParentRoute: () => rootRouteImport,
218+
} as any)
213219
const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({
214220
id: '/via-loader',
215221
path: '/via-loader',
@@ -299,6 +305,7 @@ export interface FileRoutesByFullPath {
299305
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
300306
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
301307
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
308+
'/test-head/article/$id': typeof TestHeadArticleIdRoute
302309
'/transition/count/create-resource': typeof TransitionCountCreateResourceRoute
303310
'/transition/typing/create-resource': typeof TransitionTypingCreateResourceRoute
304311
'/redirect/$target/': typeof RedirectTargetIndexRoute
@@ -335,6 +342,7 @@ export interface FileRoutesByTo {
335342
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
336343
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
337344
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
345+
'/test-head/article/$id': typeof TestHeadArticleIdRoute
338346
'/transition/count/create-resource': typeof TransitionCountCreateResourceRoute
339347
'/transition/typing/create-resource': typeof TransitionTypingCreateResourceRoute
340348
'/redirect/$target': typeof RedirectTargetIndexRoute
@@ -379,6 +387,7 @@ export interface FileRoutesById {
379387
'/posts_/$postId/deep': typeof PostsPostIdDeepRoute
380388
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
381389
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
390+
'/test-head/article/$id': typeof TestHeadArticleIdRoute
382391
'/transition/count/create-resource': typeof TransitionCountCreateResourceRoute
383392
'/transition/typing/create-resource': typeof TransitionTypingCreateResourceRoute
384393
'/redirect/$target/': typeof RedirectTargetIndexRoute
@@ -422,6 +431,7 @@ export interface FileRouteTypes {
422431
| '/posts/$postId/deep'
423432
| '/redirect/$target/via-beforeLoad'
424433
| '/redirect/$target/via-loader'
434+
| '/test-head/article/$id'
425435
| '/transition/count/create-resource'
426436
| '/transition/typing/create-resource'
427437
| '/redirect/$target/'
@@ -458,6 +468,7 @@ export interface FileRouteTypes {
458468
| '/posts/$postId/deep'
459469
| '/redirect/$target/via-beforeLoad'
460470
| '/redirect/$target/via-loader'
471+
| '/test-head/article/$id'
461472
| '/transition/count/create-resource'
462473
| '/transition/typing/create-resource'
463474
| '/redirect/$target'
@@ -501,6 +512,7 @@ export interface FileRouteTypes {
501512
| '/posts_/$postId/deep'
502513
| '/redirect/$target/via-beforeLoad'
503514
| '/redirect/$target/via-loader'
515+
| '/test-head/article/$id'
504516
| '/transition/count/create-resource'
505517
| '/transition/typing/create-resource'
506518
| '/redirect/$target/'
@@ -529,6 +541,7 @@ export interface RootRouteChildren {
529541
MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute
530542
RedirectIndexRoute: typeof RedirectIndexRoute
531543
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
544+
TestHeadArticleIdRoute: typeof TestHeadArticleIdRoute
532545
TransitionCountCreateResourceRoute: typeof TransitionCountCreateResourceRoute
533546
TransitionTypingCreateResourceRoute: typeof TransitionTypingCreateResourceRoute
534547
}
@@ -752,6 +765,13 @@ declare module '@tanstack/solid-router' {
752765
preLoaderRoute: typeof TransitionCountCreateResourceRouteImport
753766
parentRoute: typeof rootRouteImport
754767
}
768+
'/test-head/article/$id': {
769+
id: '/test-head/article/$id'
770+
path: '/test-head/article/$id'
771+
fullPath: '/test-head/article/$id'
772+
preLoaderRoute: typeof TestHeadArticleIdRouteImport
773+
parentRoute: typeof rootRouteImport
774+
}
755775
'/redirect/$target/via-loader': {
756776
id: '/redirect/$target/via-loader'
757777
path: '/via-loader'
@@ -963,6 +983,7 @@ const rootRouteChildren: RootRouteChildren = {
963983
MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute,
964984
RedirectIndexRoute: RedirectIndexRoute,
965985
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
986+
TestHeadArticleIdRoute: TestHeadArticleIdRoute,
966987
TransitionCountCreateResourceRoute: TransitionCountCreateResourceRoute,
967988
TransitionTypingCreateResourceRoute: TransitionTypingCreateResourceRoute,
968989
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createFileRoute, useRouter } from '@tanstack/solid-router'
2+
import { Show } from 'solid-js'
3+
import { fakeLogin, fakeLogout, isAuthed } from '~/utils/fake-auth'
4+
5+
const fetchArticle = async (id: string) => {
6+
await new Promise((resolve) => setTimeout(resolve, 1000))
7+
return isAuthed()
8+
? {
9+
title: `Article Title for ${id}`,
10+
content: `Article content for ${id}\n`.repeat(10),
11+
}
12+
: null
13+
}
14+
15+
export const Route = createFileRoute('/test-head/article/$id')({
16+
ssr: false, // isAuthed is ClientOnly
17+
loader: async ({ params }) => {
18+
const article = await fetchArticle(params.id)
19+
return article
20+
},
21+
head: ({ loaderData }) => ({
22+
meta: [{ title: loaderData?.title ?? 'title n/a' }],
23+
}),
24+
component: RouteComponent,
25+
})
26+
27+
function RouteComponent() {
28+
const router = useRouter()
29+
const data = Route.useLoaderData()
30+
return (
31+
<Show when={data()} fallback={<NotAccessible />}>
32+
{(article) => (
33+
<div>
34+
<div>{article().content}</div>
35+
<button
36+
type="button"
37+
onClick={() => {
38+
fakeLogout()
39+
router.invalidate()
40+
}}
41+
>
42+
Log out
43+
</button>
44+
</div>
45+
)}
46+
</Show>
47+
)
48+
}
49+
50+
function NotAccessible() {
51+
const router = useRouter()
52+
return (
53+
<div>
54+
<div>Article Not Accessible.</div>
55+
<button
56+
type="button"
57+
onClick={() => {
58+
fakeLogin()
59+
router.invalidate()
60+
}}
61+
>
62+
Log in
63+
</button>
64+
</div>
65+
)
66+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createClientOnlyFn } from '@tanstack/solid-start'
2+
3+
export { fakeLogin, fakeLogout, isAuthed }
4+
5+
const isAuthed = createClientOnlyFn(() => {
6+
const tokenValue = localStorage.getItem('auth')
7+
return tokenValue === 'good'
8+
})
9+
10+
const fakeLogin = createClientOnlyFn(() => {
11+
localStorage.setItem('auth', 'good')
12+
})
13+
14+
const fakeLogout = createClientOnlyFn(() => {
15+
localStorage.removeItem('auth')
16+
})

packages/router-core/src/load-matches.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -959,14 +959,16 @@ export async function loadMatches(arg: {
959959
if (asyncLoaderPromises.length > 0) {
960960
// Schedule re-execution after all async loaders complete (non-blocking)
961961
// Use allSettled to handle both successful and failed loaders
962-
const thisNavigationLocation = inner.location
963-
Promise.allSettled(asyncLoaderPromises).then(() => {
964-
// Only execute if this navigation is still current (not superseded by new navigation)
965-
const latestLocation = inner.router.state.location
966-
if (latestLocation === thisNavigationLocation) {
967-
executeAllHeadFns(inner)
968-
}
969-
})
962+
//
963+
// TODO! temporarily disabled to make sure solid-start/basic example can reproduce the bug
964+
// const thisNavigationLocation = inner.location
965+
// Promise.allSettled(asyncLoaderPromises).then(() => {
966+
// // Only execute if this navigation is still current (not superseded by new navigation)
967+
// const latestLocation = inner.router.state.location
968+
// if (latestLocation === thisNavigationLocation) {
969+
// executeAllHeadFns(inner)
970+
// }
971+
// })
970972
}
971973

972974
// Throw notFound after head execution

0 commit comments

Comments
 (0)