Skip to content

Commit f9dfb31

Browse files
feat: pass in router instance in client function middleware (#4723)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a1d54c4 commit f9dfb31

File tree

30 files changed

+479
-75
lines changed

30 files changed

+479
-75
lines changed

e2e/react-start/server-functions/src/routeTree.gen.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserv
2222
import { Route as ConsistentRouteImport } from './routes/consistent'
2323
import { Route as AbortSignalRouteImport } from './routes/abort-signal'
2424
import { Route as IndexRouteImport } from './routes/index'
25+
import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index'
2526
import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index'
2627
import { Route as CookiesIndexRouteImport } from './routes/cookies/index'
28+
import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router'
2729
import { Route as CookiesSetRouteImport } from './routes/cookies/set'
2830
import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name'
2931

@@ -92,6 +94,11 @@ const IndexRoute = IndexRouteImport.update({
9294
path: '/',
9395
getParentRoute: () => rootRouteImport,
9496
} as any)
97+
const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({
98+
id: '/middleware/',
99+
path: '/middleware/',
100+
getParentRoute: () => rootRouteImport,
101+
} as any)
95102
const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({
96103
id: '/formdata-redirect/',
97104
path: '/formdata-redirect/',
@@ -102,6 +109,12 @@ const CookiesIndexRoute = CookiesIndexRouteImport.update({
102109
path: '/cookies/',
103110
getParentRoute: () => rootRouteImport,
104111
} as any)
112+
const MiddlewareClientMiddlewareRouterRoute =
113+
MiddlewareClientMiddlewareRouterRouteImport.update({
114+
id: '/middleware/client-middleware-router',
115+
path: '/middleware/client-middleware-router',
116+
getParentRoute: () => rootRouteImport,
117+
} as any)
105118
const CookiesSetRoute = CookiesSetRouteImport.update({
106119
id: '/cookies/set',
107120
path: '/cookies/set',
@@ -129,8 +142,10 @@ export interface FileRoutesByFullPath {
129142
'/status': typeof StatusRoute
130143
'/submit-post-formdata': typeof SubmitPostFormdataRoute
131144
'/cookies/set': typeof CookiesSetRoute
145+
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
132146
'/cookies': typeof CookiesIndexRoute
133147
'/formdata-redirect': typeof FormdataRedirectIndexRoute
148+
'/middleware': typeof MiddlewareIndexRoute
134149
'/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute
135150
}
136151
export interface FileRoutesByTo {
@@ -148,8 +163,10 @@ export interface FileRoutesByTo {
148163
'/status': typeof StatusRoute
149164
'/submit-post-formdata': typeof SubmitPostFormdataRoute
150165
'/cookies/set': typeof CookiesSetRoute
166+
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
151167
'/cookies': typeof CookiesIndexRoute
152168
'/formdata-redirect': typeof FormdataRedirectIndexRoute
169+
'/middleware': typeof MiddlewareIndexRoute
153170
'/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute
154171
}
155172
export interface FileRoutesById {
@@ -168,8 +185,10 @@ export interface FileRoutesById {
168185
'/status': typeof StatusRoute
169186
'/submit-post-formdata': typeof SubmitPostFormdataRoute
170187
'/cookies/set': typeof CookiesSetRoute
188+
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
171189
'/cookies/': typeof CookiesIndexRoute
172190
'/formdata-redirect/': typeof FormdataRedirectIndexRoute
191+
'/middleware/': typeof MiddlewareIndexRoute
173192
'/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute
174193
}
175194
export interface FileRouteTypes {
@@ -189,8 +208,10 @@ export interface FileRouteTypes {
189208
| '/status'
190209
| '/submit-post-formdata'
191210
| '/cookies/set'
211+
| '/middleware/client-middleware-router'
192212
| '/cookies'
193213
| '/formdata-redirect'
214+
| '/middleware'
194215
| '/formdata-redirect/target/$name'
195216
fileRoutesByTo: FileRoutesByTo
196217
to:
@@ -208,8 +229,10 @@ export interface FileRouteTypes {
208229
| '/status'
209230
| '/submit-post-formdata'
210231
| '/cookies/set'
232+
| '/middleware/client-middleware-router'
211233
| '/cookies'
212234
| '/formdata-redirect'
235+
| '/middleware'
213236
| '/formdata-redirect/target/$name'
214237
id:
215238
| '__root__'
@@ -227,8 +250,10 @@ export interface FileRouteTypes {
227250
| '/status'
228251
| '/submit-post-formdata'
229252
| '/cookies/set'
253+
| '/middleware/client-middleware-router'
230254
| '/cookies/'
231255
| '/formdata-redirect/'
256+
| '/middleware/'
232257
| '/formdata-redirect/target/$name'
233258
fileRoutesById: FileRoutesById
234259
}
@@ -247,8 +272,10 @@ export interface RootRouteChildren {
247272
StatusRoute: typeof StatusRoute
248273
SubmitPostFormdataRoute: typeof SubmitPostFormdataRoute
249274
CookiesSetRoute: typeof CookiesSetRoute
275+
MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute
250276
CookiesIndexRoute: typeof CookiesIndexRoute
251277
FormdataRedirectIndexRoute: typeof FormdataRedirectIndexRoute
278+
MiddlewareIndexRoute: typeof MiddlewareIndexRoute
252279
FormdataRedirectTargetNameRoute: typeof FormdataRedirectTargetNameRoute
253280
}
254281

@@ -345,6 +372,13 @@ declare module '@tanstack/react-router' {
345372
preLoaderRoute: typeof IndexRouteImport
346373
parentRoute: typeof rootRouteImport
347374
}
375+
'/middleware/': {
376+
id: '/middleware/'
377+
path: '/middleware'
378+
fullPath: '/middleware'
379+
preLoaderRoute: typeof MiddlewareIndexRouteImport
380+
parentRoute: typeof rootRouteImport
381+
}
348382
'/formdata-redirect/': {
349383
id: '/formdata-redirect/'
350384
path: '/formdata-redirect'
@@ -359,6 +393,13 @@ declare module '@tanstack/react-router' {
359393
preLoaderRoute: typeof CookiesIndexRouteImport
360394
parentRoute: typeof rootRouteImport
361395
}
396+
'/middleware/client-middleware-router': {
397+
id: '/middleware/client-middleware-router'
398+
path: '/middleware/client-middleware-router'
399+
fullPath: '/middleware/client-middleware-router'
400+
preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport
401+
parentRoute: typeof rootRouteImport
402+
}
362403
'/cookies/set': {
363404
id: '/cookies/set'
364405
path: '/cookies/set'
@@ -391,8 +432,10 @@ const rootRouteChildren: RootRouteChildren = {
391432
StatusRoute: StatusRoute,
392433
SubmitPostFormdataRoute: SubmitPostFormdataRoute,
393434
CookiesSetRoute: CookiesSetRoute,
435+
MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute,
394436
CookiesIndexRoute: CookiesIndexRoute,
395437
FormdataRedirectIndexRoute: FormdataRedirectIndexRoute,
438+
MiddlewareIndexRoute: MiddlewareIndexRoute,
396439
FormdataRedirectTargetNameRoute: FormdataRedirectTargetNameRoute,
397440
}
398441
export const routeTree = rootRouteImport

e2e/react-start/server-functions/src/router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export function createRouter() {
1010
defaultErrorComponent: DefaultCatchBoundary,
1111
defaultNotFoundComponent: () => <NotFound />,
1212
scrollRestoration: true,
13+
context: {
14+
foo: {
15+
bar: 'baz',
16+
},
17+
},
1318
})
1419

1520
return router

e2e/react-start/server-functions/src/routes/__root.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import {
55
Link,
66
Outlet,
77
Scripts,
8-
createRootRoute,
8+
createRootRouteWithContext,
99
} from '@tanstack/react-router'
1010

1111
import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
1212
import { NotFound } from '~/components/NotFound'
1313
import appCss from '~/styles/app.css?url'
1414

15-
export const Route = createRootRoute({
15+
export const Route = createRootRouteWithContext<{ foo: { bar: string } }>()({
1616
head: () => ({
1717
meta: [
1818
{

e2e/react-start/server-functions/src/routes/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ function Home() {
8282
server function redirects when FormData is submitted (via no-JS)
8383
</Link>
8484
</li>
85+
<li>
86+
<Link to="/middleware">Server Functions Middlware E2E tests</Link>
87+
</li>
8588
</ul>
8689
</div>
8790
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { createFileRoute, useRouter } from '@tanstack/react-router'
2+
import { createMiddleware, createServerFn } from '@tanstack/react-start'
3+
import React from 'react'
4+
5+
const middleware = createMiddleware({ type: 'function' }).client(
6+
async ({ router, next }) => {
7+
return next({
8+
sendContext: {
9+
routerContext: router.options.context,
10+
},
11+
})
12+
},
13+
)
14+
15+
const serverFn = createServerFn()
16+
.middleware([middleware])
17+
.handler(({ context }) => {
18+
return context.routerContext
19+
})
20+
export const Route = createFileRoute('/middleware/client-middleware-router')({
21+
component: RouteComponent,
22+
loader: async () => ({ serverFnLoaderResult: await serverFn() }),
23+
})
24+
25+
function RouteComponent() {
26+
const [serverFnClientResult, setServerFnClientResult] = React.useState({})
27+
const { serverFnLoaderResult } = Route.useLoaderData()
28+
29+
const router = useRouter()
30+
return (
31+
<div
32+
className="p-2 m-2 grid gap-2"
33+
data-testid="client-middleware-router-route-component"
34+
>
35+
<h3>Client Middleware has access to router instance</h3>
36+
<p>
37+
This component checks that the client middleware has access to the
38+
router instance and thus its context.
39+
</p>
40+
<div>
41+
It should return{' '}
42+
<code>
43+
<pre data-testid="expected-server-fn-result">
44+
{JSON.stringify(router.options.context)}
45+
</pre>
46+
</code>
47+
</div>
48+
<p>
49+
serverFn when invoked in the loader returns:
50+
<br />
51+
<span data-testid="serverFn-loader-result">
52+
{JSON.stringify(serverFnClientResult)}
53+
</span>
54+
</p>
55+
<p>
56+
serverFn when invoked on the client returns:
57+
<br />
58+
<span data-testid="serverFn-client-result">
59+
{JSON.stringify(serverFnLoaderResult)}
60+
</span>
61+
</p>
62+
<button
63+
data-testid="btn-serverFn"
64+
type="button"
65+
className="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
66+
onClick={() => {
67+
serverFn().then(setServerFnClientResult)
68+
}}
69+
>
70+
Invoke Server Function
71+
</button>
72+
</div>
73+
)
74+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
3+
export const Route = createFileRoute('/middleware/')({
4+
component: RouteComponent,
5+
})
6+
7+
function RouteComponent() {
8+
return (
9+
<div className="p-8">
10+
<h1 className="font-bold text-lg">
11+
Server functions middleware E2E tests
12+
</h1>
13+
<ul className="list-disc p-4">
14+
<li>
15+
<Route.Link
16+
to="./client-middleware-router"
17+
data-testid="client-middleware-router-link"
18+
>
19+
Client Middleware has access to router instance
20+
</Route.Link>
21+
</li>
22+
</ul>
23+
</div>
24+
)
25+
}

e2e/react-start/server-functions/tests/server-functions.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,36 @@ test('raw response', async ({ page }) => {
339339
expect(page.url().endsWith(`/formdata-redirect/target/${expected}`))
340340
})
341341
})
342+
343+
test.describe('middleware', () => {
344+
test.describe('client middleware should have access to router context via the router instance', () => {
345+
async function runTest(page: Page) {
346+
await page.waitForLoadState('networkidle')
347+
348+
const expected =
349+
(await page.getByTestId('expected-server-fn-result').textContent()) ||
350+
''
351+
expect(expected).not.toBe('')
352+
353+
await page.getByTestId('btn-serverFn').click()
354+
await page.waitForLoadState('networkidle')
355+
await expect(page.getByTestId('serverFn-loader-result')).toContainText(
356+
expected,
357+
)
358+
await expect(page.getByTestId('serverFn-client-result')).toContainText(
359+
expected,
360+
)
361+
}
362+
363+
test('direct visit', async ({ page }) => {
364+
await page.goto('/middleware/client-middleware-router')
365+
await runTest(page)
366+
})
367+
368+
test('client navigation', async ({ page }) => {
369+
await page.goto('/middleware')
370+
await page.getByTestId('client-middleware-router-link').click()
371+
await runTest(page)
372+
})
373+
})
374+
})

labeler-config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
'package: start-server-functions-server':
9595
- changed-files:
9696
- any-glob-to-any-file: 'packages/start-server-functions-server/**/*'
97+
'package: start-storage-context':
98+
- changed-files:
99+
- any-glob-to-any-file: 'packages/start-storage-context/**/*'
97100
'package: valibot-adapter':
98101
- changed-files:
99102
- any-glob-to-any-file: 'packages/valibot-adapter/**/*'

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"@tanstack/start-plugin-core": "workspace:*",
113113
"@tanstack/start-client-core": "workspace:*",
114114
"@tanstack/start-server-core": "workspace:*",
115+
"@tanstack/start-storage-context": "workspace:*",
115116
"@tanstack/eslint-plugin-router": "workspace:*",
116117
"@tanstack/server-functions-plugin": "workspace:*",
117118
"@tanstack/directive-functions-plugin": "workspace:*",

packages/react-start-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
],
2626
"scripts": {
2727
"clean": "rimraf ./dist && rimraf ./coverage",
28-
"test": "pnpm test:deps && pnpm test:eslint && pnpm test:types && pnpm test:build && pnpm test:unit",
28+
"test": "pnpm test:eslint && pnpm test:types && pnpm test:build && pnpm test:unit",
2929
"test:unit": "vitest",
3030
"test:unit:dev": "vitest --watch",
3131
"test:eslint": "eslint ./src",

0 commit comments

Comments
 (0)