Skip to content

Commit b4fb853

Browse files
authored
Merge branch 'main' into kosciolek/4599
2 parents 1737678 + db8ae23 commit b4fb853

File tree

156 files changed

+1762
-400
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

156 files changed

+1762
-400
lines changed

docs/start/framework/react/spa-mode.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,18 @@ Customizing the HTML output of the SPA shell can be useful if you want to:
142142
- Provide a custom pending fallback component
143143
- Change literally anything about the shell's HTML, CSS, and JS
144144

145-
To make this process simple, an `isShell` boolean can be found on the `router` instance:
145+
To make this process simple, an `isShell()` function can be found on the `router` instance:
146146

147147
```tsx
148148
// src/routes/root.tsx
149149
export default function Root() {
150-
const isShell = useRouter().isShell
150+
const isShell = useRouter().isShell()
151151

152152
if (isShell) console.log('Rendering the shell!')
153153
}
154154
```
155155

156-
You can use this boolean to conditionally render different UI based on whether the current route is a shell or not, but keep in mind that after hydrating the shell, the router will immediately navigate to the first route and the `isShell` boolean will be `false`. **This could produce flashes of unstyled content if not handled properly.**
156+
You can use this boolean to conditionally render different UI based on whether the current route is a shell or not, but keep in mind that after hydrating the shell, the router will immediately navigate to the first route and `isShell()` will return `false`. **This could produce flashes of unstyled content if not handled properly.**
157157

158158
## Dynamic Data in your Shell
159159

e2e/react-start/custom-basepath/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { createServerRootRoute } from '@tanstack/react-start/server'
1313
import { Route as rootRouteImport } from './routes/__root'
1414
import { Route as UsersRouteImport } from './routes/users'
1515
import { Route as PostsRouteImport } from './routes/posts'
16+
import { Route as LogoutRouteImport } from './routes/logout'
1617
import { Route as DeferredRouteImport } from './routes/deferred'
1718
import { Route as IndexRouteImport } from './routes/index'
1819
import { Route as UsersIndexRouteImport } from './routes/users.index'
@@ -35,6 +36,11 @@ const PostsRoute = PostsRouteImport.update({
3536
path: '/posts',
3637
getParentRoute: () => rootRouteImport,
3738
} as any)
39+
const LogoutRoute = LogoutRouteImport.update({
40+
id: '/logout',
41+
path: '/logout',
42+
getParentRoute: () => rootRouteImport,
43+
} as any)
3844
const DeferredRoute = DeferredRouteImport.update({
3945
id: '/deferred',
4046
path: '/deferred',
@@ -84,6 +90,7 @@ const ApiUsersIdServerRoute = ApiUsersIdServerRouteImport.update({
8490
export interface FileRoutesByFullPath {
8591
'/': typeof IndexRoute
8692
'/deferred': typeof DeferredRoute
93+
'/logout': typeof LogoutRoute
8794
'/posts': typeof PostsRouteWithChildren
8895
'/users': typeof UsersRouteWithChildren
8996
'/posts/$postId': typeof PostsPostIdRoute
@@ -95,6 +102,7 @@ export interface FileRoutesByFullPath {
95102
export interface FileRoutesByTo {
96103
'/': typeof IndexRoute
97104
'/deferred': typeof DeferredRoute
105+
'/logout': typeof LogoutRoute
98106
'/posts/$postId': typeof PostsPostIdRoute
99107
'/users/$userId': typeof UsersUserIdRoute
100108
'/posts': typeof PostsIndexRoute
@@ -105,6 +113,7 @@ export interface FileRoutesById {
105113
__root__: typeof rootRouteImport
106114
'/': typeof IndexRoute
107115
'/deferred': typeof DeferredRoute
116+
'/logout': typeof LogoutRoute
108117
'/posts': typeof PostsRouteWithChildren
109118
'/users': typeof UsersRouteWithChildren
110119
'/posts/$postId': typeof PostsPostIdRoute
@@ -118,6 +127,7 @@ export interface FileRouteTypes {
118127
fullPaths:
119128
| '/'
120129
| '/deferred'
130+
| '/logout'
121131
| '/posts'
122132
| '/users'
123133
| '/posts/$postId'
@@ -129,6 +139,7 @@ export interface FileRouteTypes {
129139
to:
130140
| '/'
131141
| '/deferred'
142+
| '/logout'
132143
| '/posts/$postId'
133144
| '/users/$userId'
134145
| '/posts'
@@ -138,6 +149,7 @@ export interface FileRouteTypes {
138149
| '__root__'
139150
| '/'
140151
| '/deferred'
152+
| '/logout'
141153
| '/posts'
142154
| '/users'
143155
| '/posts/$postId'
@@ -150,6 +162,7 @@ export interface FileRouteTypes {
150162
export interface RootRouteChildren {
151163
IndexRoute: typeof IndexRoute
152164
DeferredRoute: typeof DeferredRoute
165+
LogoutRoute: typeof LogoutRoute
153166
PostsRoute: typeof PostsRouteWithChildren
154167
UsersRoute: typeof UsersRouteWithChildren
155168
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
@@ -195,6 +208,13 @@ declare module '@tanstack/react-router' {
195208
preLoaderRoute: typeof PostsRouteImport
196209
parentRoute: typeof rootRouteImport
197210
}
211+
'/logout': {
212+
id: '/logout'
213+
path: '/logout'
214+
fullPath: '/logout'
215+
preLoaderRoute: typeof LogoutRouteImport
216+
parentRoute: typeof rootRouteImport
217+
}
198218
'/deferred': {
199219
id: '/deferred'
200220
path: '/deferred'
@@ -304,6 +324,7 @@ const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren(
304324
const rootRouteChildren: RootRouteChildren = {
305325
IndexRoute: IndexRoute,
306326
DeferredRoute: DeferredRoute,
327+
LogoutRoute: LogoutRoute,
307328
PostsRoute: PostsRouteWithChildren,
308329
UsersRoute: UsersRouteWithChildren,
309330
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createFileRoute, redirect } from '@tanstack/react-router'
2+
import { createServerFn } from '@tanstack/react-start'
3+
4+
const logoutFn = createServerFn({
5+
method: 'POST',
6+
}).handler(async () => {
7+
// do logout stuff here
8+
throw redirect({
9+
to: '/',
10+
})
11+
})
12+
13+
export const Route = createFileRoute('/logout')({
14+
component: Home,
15+
})
16+
17+
function Home() {
18+
return (
19+
<div className="p-2">
20+
<h3>Logout Page</h3>
21+
<p>
22+
This form tests that server function URLs correctly include the app's
23+
basepath. The form action should be '/custom/basepath/_serverFn/...' not
24+
just '/_serverFn/...'
25+
</p>
26+
<form action={logoutFn.url} method="POST">
27+
<input type="hidden" name="csrfToken" value="123abc" />
28+
<button type="submit">Logout</button>
29+
</form>
30+
</div>
31+
)
32+
}

e2e/react-start/custom-basepath/tests/navigation.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,16 @@ test('Should change title on client side navigation', async ({ page }) => {
3232

3333
await expect(page).toHaveTitle('Posts page')
3434
})
35+
36+
test('Server function URLs correctly include app basepath', async ({
37+
page,
38+
}) => {
39+
await page.goto('/logout')
40+
41+
const form = page.locator('form')
42+
const actionUrl = await form.getAttribute('action')
43+
44+
expect(actionUrl).toBe(
45+
'/custom/basepath/_serverFn/src_routes_logout_tsx--logoutFn_createServerFn_handler',
46+
)
47+
})

e2e/react-start/selective-ssr/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@tanstack/react-router": "workspace:^",
15+
"@tanstack/react-router-devtools": "workspace:^",
1516
"@tanstack/react-start": "workspace:^",
1617
"react": "^19.0.0",
1718
"react-dom": "^19.0.0",

e2e/react-start/selective-ssr/src/routes/__root.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
/// <reference types="vite/client" />
22
import * as React from 'react'
33
import {
4+
ClientOnly,
45
HeadContent,
56
Link,
67
Outlet,
78
Scripts,
89
createRootRoute,
10+
useRouterState,
911
} from '@tanstack/react-router'
1012
import { z } from 'zod'
1113
import { ssrSchema } from '~/search'
1214
import appCss from '~/styles/app.css?url'
15+
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
1316

1417
export const Route = createRootRoute({
1518
head: () => ({
@@ -113,9 +116,14 @@ export const Route = createRootRoute({
113116
</div>
114117
)
115118
},
119+
pendingComponent: () => <div>__root Loading...</div>,
116120
})
117121

118122
function RootDocument({ children }: { children: React.ReactNode }) {
123+
const { isLoading, status } = useRouterState({
124+
select: (state) => ({ isLoading: state.isLoading, status: state.status }),
125+
structuralSharing: true,
126+
})
119127
return (
120128
<html>
121129
<head>
@@ -134,8 +142,19 @@ function RootDocument({ children }: { children: React.ReactNode }) {
134142
</Link>
135143
</div>
136144
<hr />
145+
<ClientOnly>
146+
<div>
147+
router isLoading:{' '}
148+
<b data-testid="router-isLoading">{isLoading ? 'true' : 'false'}</b>
149+
</div>
150+
<div>
151+
router status: <b data-testid="router-status">{status}</b>
152+
</div>
153+
</ClientOnly>
154+
<hr />
137155
{children}
138156
<Scripts />
157+
<TanStackRouterDevtools position="bottom-right" />
139158
</body>
140159
</html>
141160
)

e2e/react-start/selective-ssr/src/routes/index.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,26 @@ const testCases = [
9191
},
9292
}),
9393
},
94+
{
95+
link: linkOptions({
96+
...baseTestCase,
97+
search: {
98+
root: {
99+
ssr: true,
100+
expected: { data: 'server', render: 'server-and-client' },
101+
},
102+
posts: {
103+
ssr: 'data-only',
104+
expected: { data: 'server', render: 'client-only' },
105+
},
106+
postId: {
107+
ssr: undefined,
108+
109+
expected: { data: 'server', render: 'client-only' },
110+
},
111+
},
112+
}),
113+
},
94114
{
95115
link: linkOptions({
96116
...baseTestCase,

e2e/react-start/selective-ssr/src/routes/posts.$postId.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,5 @@ export const Route = createFileRoute('/posts/$postId')({
8787
</div>
8888
)
8989
},
90+
pendingComponent: () => <div>$postId Loading...</div>,
9091
})

e2e/react-start/selective-ssr/src/routes/posts.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,5 @@ export const Route = createFileRoute('/posts')({
8989
</div>
9090
)
9191
},
92+
pendingComponent: () => <div>posts Loading...</div>,
9293
})

e2e/react-start/selective-ssr/tests/app.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from '@playwright/test'
22
import { test } from './fixture'
33

4-
const testCount = 6
4+
const testCount = 7
55

66
test.describe('selective ssr', () => {
77
test('testcount matches', async ({ page }) => {
@@ -34,6 +34,8 @@ test.describe('selective ssr', () => {
3434
)
3535
}),
3636
)
37+
await expect(page.getByTestId('router-isLoading')).toContainText('false')
38+
await expect(page.getByTestId('router-status')).toContainText('idle')
3739
})
3840
}
3941
})

0 commit comments

Comments
 (0)