Skip to content
Open
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
42 changes: 42 additions & 0 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Route as TypeOnlyReexportRouteImport } from './routes/type-only-reexpor
import { Route as UsersRouteImport } from './routes/users'
import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
import { Route as ApiUsersRouteImport } from './routes/api.users'
import { Route as Issue6221DashboardRouteImport } from './routes/issue-6221.dashboard'
import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index'
import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target'
import { Route as NotFoundIndexRouteImport } from './routes/not-found/index'
Expand Down Expand Up @@ -61,6 +62,7 @@ import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b'
import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id'
import { Route as Issue6221ArticleIdRouteImport } from './routes/issue-6221.article.$id'
import { Route as NotFoundDeepIndexRouteImport } from './routes/not-found/deep/index'
import { Route as NotFoundDeepBRouteRouteImport } from './routes/not-found/deep/b/route'
import { Route as NotFoundParentBoundaryIndexRouteImport } from './routes/not-found/parent-boundary/index'
Expand Down Expand Up @@ -179,6 +181,11 @@ const ApiUsersRoute = ApiUsersRouteImport.update({
path: '/api/users',
getParentRoute: () => rootRouteImport,
} as any)
const Issue6221DashboardRoute = Issue6221DashboardRouteImport.update({
id: '/issue-6221/dashboard',
path: '/issue-6221/dashboard',
getParentRoute: () => rootRouteImport,
} as any)
const MultiCookieRedirectIndexRoute =
MultiCookieRedirectIndexRouteImport.update({
id: '/multi-cookie-redirect/',
Expand Down Expand Up @@ -346,6 +353,11 @@ const ApiUsersIdRoute = ApiUsersIdRouteImport.update({
path: '/$id',
getParentRoute: () => ApiUsersRoute,
} as any)
const Issue6221ArticleIdRoute = Issue6221ArticleIdRouteImport.update({
id: '/issue-6221/article/$id',
path: '/issue-6221/article/$id',
getParentRoute: () => rootRouteImport,
} as any)
const NotFoundDeepIndexRoute = NotFoundDeepIndexRouteImport.update({
id: '/',
path: '/',
Expand Down Expand Up @@ -468,6 +480,7 @@ export interface FileRoutesByFullPath {
'/not-found/parent-boundary': typeof NotFoundParentBoundaryRouteRouteWithChildren
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -498,6 +511,7 @@ export interface FileRoutesByFullPath {
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -531,6 +545,7 @@ export interface FileRoutesByTo {
'/type-only-reexport': typeof TypeOnlyReexportRoute
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -560,6 +575,7 @@ export interface FileRoutesByTo {
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -602,6 +618,7 @@ export interface FileRoutesById {
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -632,6 +649,7 @@ export interface FileRoutesById {
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
'/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts_/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -674,6 +692,7 @@ export interface FileRouteTypes {
| '/not-found/parent-boundary'
| '/specialChars/malformed'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -704,6 +723,7 @@ export interface FileRouteTypes {
| '/layout-a'
| '/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -737,6 +757,7 @@ export interface FileRouteTypes {
| '/type-only-reexport'
| '/specialChars/malformed'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -766,6 +787,7 @@ export interface FileRouteTypes {
| '/layout-a'
| '/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -807,6 +829,7 @@ export interface FileRouteTypes {
| '/specialChars/malformed'
| '/_layout/_layout-2'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -837,6 +860,7 @@ export interface FileRouteTypes {
| '/_layout/_layout-2/layout-a'
| '/_layout/_layout-2/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts_/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -876,10 +900,12 @@ export interface RootRouteChildren {
TypeOnlyReexportRoute: typeof TypeOnlyReexportRoute
UsersRoute: typeof UsersRouteWithChildren
ApiUsersRoute: typeof ApiUsersRouteWithChildren
Issue6221DashboardRoute: typeof Issue6221DashboardRoute
MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute
RedirectIndexRoute: typeof RedirectIndexRoute
Issue6221ArticleIdRoute: typeof Issue6221ArticleIdRoute
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
FooBarQuxHereRoute: typeof FooBarQuxHereRouteWithChildren
}
Expand Down Expand Up @@ -1026,6 +1052,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiUsersRouteImport
parentRoute: typeof rootRouteImport
}
'/issue-6221/dashboard': {
id: '/issue-6221/dashboard'
path: '/issue-6221/dashboard'
fullPath: '/issue-6221/dashboard'
preLoaderRoute: typeof Issue6221DashboardRouteImport
parentRoute: typeof rootRouteImport
}
'/multi-cookie-redirect/': {
id: '/multi-cookie-redirect/'
path: '/multi-cookie-redirect'
Expand Down Expand Up @@ -1250,6 +1283,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiUsersIdRouteImport
parentRoute: typeof ApiUsersRoute
}
'/issue-6221/article/$id': {
id: '/issue-6221/article/$id'
path: '/issue-6221/article/$id'
fullPath: '/issue-6221/article/$id'
preLoaderRoute: typeof Issue6221ArticleIdRouteImport
parentRoute: typeof rootRouteImport
}
'/not-found/deep/': {
id: '/not-found/deep/'
path: '/'
Expand Down Expand Up @@ -1648,10 +1688,12 @@ const rootRouteChildren: RootRouteChildren = {
TypeOnlyReexportRoute: TypeOnlyReexportRoute,
UsersRoute: UsersRouteWithChildren,
ApiUsersRoute: ApiUsersRouteWithChildren,
Issue6221DashboardRoute: Issue6221DashboardRoute,
MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute,
RedirectTargetRoute: RedirectTargetRouteWithChildren,
MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute,
RedirectIndexRoute: RedirectIndexRoute,
Issue6221ArticleIdRoute: Issue6221ArticleIdRoute,
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
FooBarQuxHereRoute: FooBarQuxHereRouteWithChildren,
}
Expand Down
42 changes: 42 additions & 0 deletions e2e/react-start/basic/src/routes/issue-6221.article.$id.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import { createClientOnlyFn } from '@tanstack/react-start'

const isAuthed = createClientOnlyFn(
() => localStorage.getItem('issue-6221-auth') === 'good',
)

const fetchArticle = async (id: string) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return isAuthed()
? {
title: `Article Title for ${id}`,
content: `Article content for ${id}`,
}
: null
}

export const Route = createFileRoute('/issue-6221/article/$id')({
ssr: false,
loader: ({ params }) => fetchArticle(params.id),
head: ({ loaderData }) => ({
meta: [{ title: loaderData?.title ?? 'title n/a' }],
}),
component: Article,
})

function Article() {
const article = Route.useLoaderData()

return (
<div>
<Link to="/issue-6221/dashboard" data-testid="issue-6221-dashboard-link">
Dashboard
</Link>
{article ? (
<div data-testid="issue-6221-article">{article.content}</div>
) : (
<div>Article Not Accessible.</div>
)}
</div>
)
}
6 changes: 6 additions & 0 deletions e2e/react-start/basic/src/routes/issue-6221.dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/issue-6221/dashboard')({
head: () => ({ meta: [{ title: 'Dashboard' }] }),
component: () => <div data-testid="issue-6221-dashboard">Dashboard</div>,
})
41 changes: 41 additions & 0 deletions e2e/react-start/basic/tests/issue-6221-head.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect } from '@playwright/test'
import { test } from '@tanstack/router-e2e-utils'
import { isPreview } from './utils/isPreview'
import { isSpaMode } from './utils/isSpaMode'

// Ported to React from the regression app in
// https://github.com/TanStack/router/pull/6222
test.skip(
isSpaMode || isPreview,
'head() coverage for an ssr:false route requires the SSR test mode',
)

test('browser back uses fresh loaderData for both the body and head (#6221)', async ({
page,
}) => {
await page.goto('/')
await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good'))

await page.goto('/issue-6221/article/1')

await expect(page.getByTestId('issue-6221-article')).toHaveText(
'Article content for 1',
)
await expect(page).toHaveTitle('Article Title for 1')

await page.evaluate(() => localStorage.removeItem('issue-6221-auth'))
await page.reload()
await expect(page.getByText('Article Not Accessible.')).toBeVisible()
await expect(page).toHaveTitle('title n/a')

await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good'))
await page.getByTestId('issue-6221-dashboard-link').click()
await expect(page.getByTestId('issue-6221-dashboard')).toBeVisible()
await expect(page).toHaveTitle('Dashboard')

await page.goBack()
await expect(page.getByTestId('issue-6221-article')).toHaveText(
'Article content for 1',
)
await expect(page).toHaveTitle('Article Title for 1')
})
11 changes: 7 additions & 4 deletions packages/react-router/src/Matches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CatchBoundary } from './CatchBoundary'
import { useRouter } from './useRouter'
import { useStructuralSharing } from './useMatch'
import { useLayoutEffect } from './utils'
import { Transitioner } from './Transitioner'
import { Transitioner, settleOwner } from './Transitioner'
import { matchContext } from './matchContext'
import { Match, renderPending } from './Match'
import { SafeFragment } from './SafeFragment'
Expand Down Expand Up @@ -73,17 +73,20 @@ export function Matches() {

function MatchesInner() {
const router = useRouter()
const acknowledgement = router._rendered!
const matches =
(isServer ?? router.isServer)
? router.stores.matches.get()
: // eslint-disable-next-line react-hooks/rules-of-hooks
useStore(router.stores.matches, (value) => value)
useStore(router.stores.matches, (value) => acknowledgement[0] ?? value)
const match = matches[0]
const routeId = match?.routeId

useLayoutEffect(() => {
router._rendered!(matches)
}, [matches, router])
if (acknowledgement[0] === matches) {
settleOwner(acknowledgement, true)
}
}, [acknowledgement, matches])

const matchComponent = routeId ? <Match routeId={routeId} /> : null

Expand Down
Loading
Loading