Skip to content

Commit 24ff895

Browse files
hakanshehunlynzaad
andauthored
fix(router-core): correctly resolve custom params from declarative masks when building the masked url (#5756)
* Use params from declarative masks * Add tests for masks * Include the necessary changes * Update mask tests * Add e2e tests * minor nitpicks --------- Co-authored-by: Nico Lynzaad <nlynzaad@zylangroup.com>
1 parent afb4b24 commit 24ff895

File tree

16 files changed

+1017
-4
lines changed

16 files changed

+1017
-4
lines changed

e2e/react-router/basic-file-based/src/main.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom/client'
3-
import { RouterProvider, createRouter } from '@tanstack/react-router'
3+
import {
4+
RouterProvider,
5+
createRouteMask,
6+
createRouter,
7+
} from '@tanstack/react-router'
48
import { routeTree } from './routeTree.gen'
59
import './styles.css'
610

11+
const mask = createRouteMask({
12+
routeTree,
13+
from: '/masks/admin/$userId',
14+
to: '/masks/public/$username',
15+
params: (prev) => ({
16+
username: `user-${prev.userId}`,
17+
}),
18+
})
19+
720
// Set up a Router instance
821
const router = createRouter({
922
routeTree,
1023
defaultPreload: 'intent',
1124
defaultStaleTime: 5000,
1225
scrollRestoration: true,
26+
routeMasks: [mask],
1327
})
1428

1529
// Register things for typesafety

e2e/react-router/basic-file-based/src/routeTree.gen.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as RemountDepsRouteImport } from './routes/remountDeps'
1313
import { Route as PostsRouteImport } from './routes/posts'
1414
import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps'
15+
import { Route as MasksRouteImport } from './routes/masks'
1516
import { Route as HoverPreloadHashRouteImport } from './routes/hover-preload-hash'
1617
import { Route as EditingBRouteImport } from './routes/editing-b'
1718
import { Route as EditingARouteImport } from './routes/editing-a'
@@ -67,6 +68,8 @@ import { Route as ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport } f
6768
import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$'
6869
import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix'
6970
import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}'
71+
import { Route as MasksPublicUsernameRouteImport } from './routes/masks.public.$username'
72+
import { Route as MasksAdminUserIdRouteImport } from './routes/masks.admin.$userId'
7073
import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b'
7174
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
7275
import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside'
@@ -126,6 +129,11 @@ const NotRemountDepsRoute = NotRemountDepsRouteImport.update({
126129
path: '/notRemountDeps',
127130
getParentRoute: () => rootRouteImport,
128131
} as any)
132+
const MasksRoute = MasksRouteImport.update({
133+
id: '/masks',
134+
path: '/masks',
135+
getParentRoute: () => rootRouteImport,
136+
} as any)
129137
const HoverPreloadHashRoute = HoverPreloadHashRouteImport.update({
130138
id: '/hover-preload-hash',
131139
path: '/hover-preload-hash',
@@ -417,6 +425,16 @@ const ParamsPsNamedPrefixChar123fooChar125Route =
417425
path: '/params-ps/named/prefix{$foo}',
418426
getParentRoute: () => rootRouteImport,
419427
} as any)
428+
const MasksPublicUsernameRoute = MasksPublicUsernameRouteImport.update({
429+
id: '/public/$username',
430+
path: '/public/$username',
431+
getParentRoute: () => MasksRoute,
432+
} as any)
433+
const MasksAdminUserIdRoute = MasksAdminUserIdRouteImport.update({
434+
id: '/admin/$userId',
435+
path: '/admin/$userId',
436+
getParentRoute: () => MasksRoute,
437+
} as any)
420438
const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({
421439
id: '/layout-b',
422440
path: '/layout-b',
@@ -666,6 +684,7 @@ export interface FileRoutesByFullPath {
666684
'/editing-a': typeof EditingARoute
667685
'/editing-b': typeof EditingBRoute
668686
'/hover-preload-hash': typeof HoverPreloadHashRoute
687+
'/masks': typeof MasksRouteWithChildren
669688
'/notRemountDeps': typeof NotRemountDepsRoute
670689
'/posts': typeof PostsRouteWithChildren
671690
'/remountDeps': typeof RemountDepsRoute
@@ -700,6 +719,8 @@ export interface FileRoutesByFullPath {
700719
'/subfolder/inside': typeof groupSubfolderInsideRoute
701720
'/layout-a': typeof LayoutLayout2LayoutARoute
702721
'/layout-b': typeof LayoutLayout2LayoutBRoute
722+
'/masks/admin/$userId': typeof MasksAdminUserIdRoute
723+
'/masks/public/$username': typeof MasksPublicUsernameRoute
703724
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
704725
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
705726
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -765,6 +786,7 @@ export interface FileRoutesByTo {
765786
'/editing-a': typeof EditingARoute
766787
'/editing-b': typeof EditingBRoute
767788
'/hover-preload-hash': typeof HoverPreloadHashRoute
789+
'/masks': typeof MasksRouteWithChildren
768790
'/notRemountDeps': typeof NotRemountDepsRoute
769791
'/remountDeps': typeof RemountDepsRoute
770792
'/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren
@@ -792,6 +814,8 @@ export interface FileRoutesByTo {
792814
'/subfolder/inside': typeof groupSubfolderInsideRoute
793815
'/layout-a': typeof LayoutLayout2LayoutARoute
794816
'/layout-b': typeof LayoutLayout2LayoutBRoute
817+
'/masks/admin/$userId': typeof MasksAdminUserIdRoute
818+
'/masks/public/$username': typeof MasksPublicUsernameRoute
795819
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
796820
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
797821
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -858,6 +882,7 @@ export interface FileRoutesById {
858882
'/editing-a': typeof EditingARoute
859883
'/editing-b': typeof EditingBRoute
860884
'/hover-preload-hash': typeof HoverPreloadHashRoute
885+
'/masks': typeof MasksRouteWithChildren
861886
'/notRemountDeps': typeof NotRemountDepsRoute
862887
'/posts': typeof PostsRouteWithChildren
863888
'/remountDeps': typeof RemountDepsRoute
@@ -894,6 +919,8 @@ export interface FileRoutesById {
894919
'/(group)/subfolder/inside': typeof groupSubfolderInsideRoute
895920
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
896921
'/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
922+
'/masks/admin/$userId': typeof MasksAdminUserIdRoute
923+
'/masks/public/$username': typeof MasksPublicUsernameRoute
897924
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
898925
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
899926
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -962,6 +989,7 @@ export interface FileRouteTypes {
962989
| '/editing-a'
963990
| '/editing-b'
964991
| '/hover-preload-hash'
992+
| '/masks'
965993
| '/notRemountDeps'
966994
| '/posts'
967995
| '/remountDeps'
@@ -996,6 +1024,8 @@ export interface FileRouteTypes {
9961024
| '/subfolder/inside'
9971025
| '/layout-a'
9981026
| '/layout-b'
1027+
| '/masks/admin/$userId'
1028+
| '/masks/public/$username'
9991029
| '/params-ps/named/prefix{$foo}'
10001030
| '/params-ps/named/{$foo}suffix'
10011031
| '/params-ps/wildcard/$'
@@ -1061,6 +1091,7 @@ export interface FileRouteTypes {
10611091
| '/editing-a'
10621092
| '/editing-b'
10631093
| '/hover-preload-hash'
1094+
| '/masks'
10641095
| '/notRemountDeps'
10651096
| '/remountDeps'
10661097
| '/non-nested/deep'
@@ -1088,6 +1119,8 @@ export interface FileRouteTypes {
10881119
| '/subfolder/inside'
10891120
| '/layout-a'
10901121
| '/layout-b'
1122+
| '/masks/admin/$userId'
1123+
| '/masks/public/$username'
10911124
| '/params-ps/named/prefix{$foo}'
10921125
| '/params-ps/named/{$foo}suffix'
10931126
| '/params-ps/wildcard/$'
@@ -1153,6 +1186,7 @@ export interface FileRouteTypes {
11531186
| '/editing-a'
11541187
| '/editing-b'
11551188
| '/hover-preload-hash'
1189+
| '/masks'
11561190
| '/notRemountDeps'
11571191
| '/posts'
11581192
| '/remountDeps'
@@ -1189,6 +1223,8 @@ export interface FileRouteTypes {
11891223
| '/(group)/subfolder/inside'
11901224
| '/_layout/_layout-2/layout-a'
11911225
| '/_layout/_layout-2/layout-b'
1226+
| '/masks/admin/$userId'
1227+
| '/masks/public/$username'
11921228
| '/params-ps/named/prefix{$foo}'
11931229
| '/params-ps/named/{$foo}suffix'
11941230
| '/params-ps/wildcard/$'
@@ -1257,6 +1293,7 @@ export interface RootRouteChildren {
12571293
EditingARoute: typeof EditingARoute
12581294
EditingBRoute: typeof EditingBRoute
12591295
HoverPreloadHashRoute: typeof HoverPreloadHashRoute
1296+
MasksRoute: typeof MasksRouteWithChildren
12601297
NotRemountDepsRoute: typeof NotRemountDepsRoute
12611298
PostsRoute: typeof PostsRouteWithChildren
12621299
RemountDepsRoute: typeof RemountDepsRoute
@@ -1313,6 +1350,13 @@ declare module '@tanstack/react-router' {
13131350
preLoaderRoute: typeof NotRemountDepsRouteImport
13141351
parentRoute: typeof rootRouteImport
13151352
}
1353+
'/masks': {
1354+
id: '/masks'
1355+
path: '/masks'
1356+
fullPath: '/masks'
1357+
preLoaderRoute: typeof MasksRouteImport
1358+
parentRoute: typeof rootRouteImport
1359+
}
13161360
'/hover-preload-hash': {
13171361
id: '/hover-preload-hash'
13181362
path: '/hover-preload-hash'
@@ -1698,6 +1742,20 @@ declare module '@tanstack/react-router' {
16981742
preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport
16991743
parentRoute: typeof rootRouteImport
17001744
}
1745+
'/masks/public/$username': {
1746+
id: '/masks/public/$username'
1747+
path: '/public/$username'
1748+
fullPath: '/masks/public/$username'
1749+
preLoaderRoute: typeof MasksPublicUsernameRouteImport
1750+
parentRoute: typeof MasksRoute
1751+
}
1752+
'/masks/admin/$userId': {
1753+
id: '/masks/admin/$userId'
1754+
path: '/admin/$userId'
1755+
fullPath: '/masks/admin/$userId'
1756+
preLoaderRoute: typeof MasksAdminUserIdRouteImport
1757+
parentRoute: typeof MasksRoute
1758+
}
17011759
'/_layout/_layout-2/layout-b': {
17021760
id: '/_layout/_layout-2/layout-b'
17031761
path: '/layout-b'
@@ -2262,6 +2320,18 @@ const LayoutRouteChildren: LayoutRouteChildren = {
22622320
const LayoutRouteWithChildren =
22632321
LayoutRoute._addFileChildren(LayoutRouteChildren)
22642322

2323+
interface MasksRouteChildren {
2324+
MasksAdminUserIdRoute: typeof MasksAdminUserIdRoute
2325+
MasksPublicUsernameRoute: typeof MasksPublicUsernameRoute
2326+
}
2327+
2328+
const MasksRouteChildren: MasksRouteChildren = {
2329+
MasksAdminUserIdRoute: MasksAdminUserIdRoute,
2330+
MasksPublicUsernameRoute: MasksPublicUsernameRoute,
2331+
}
2332+
2333+
const MasksRouteWithChildren = MasksRoute._addFileChildren(MasksRouteChildren)
2334+
22652335
interface PostsRouteChildren {
22662336
PostsPostIdRoute: typeof PostsPostIdRoute
22672337
PostsIndexRoute: typeof PostsIndexRoute
@@ -2423,6 +2493,7 @@ const rootRouteChildren: RootRouteChildren = {
24232493
EditingARoute: EditingARoute,
24242494
EditingBRoute: EditingBRoute,
24252495
HoverPreloadHashRoute: HoverPreloadHashRoute,
2496+
MasksRoute: MasksRouteWithChildren,
24262497
NotRemountDepsRoute: NotRemountDepsRoute,
24272498
PostsRoute: PostsRouteWithChildren,
24282499
RemountDepsRoute: RemountDepsRoute,

e2e/react-router/basic-file-based/src/routes/__root.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ function RootComponent() {
146146
}}
147147
>
148148
This Route Does Not Exist
149+
</Link>{' '}
150+
<Link
151+
to="/masks"
152+
data-testid="link-to-masks"
153+
activeProps={{
154+
className: 'font-bold',
155+
}}
156+
>
157+
Masks
149158
</Link>
150159
</div>
151160
<hr />
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
3+
export const Route = createFileRoute('/masks/admin/$userId')({
4+
component: AdminUserRoute,
5+
})
6+
7+
function AdminUserRoute() {
8+
const params = Route.useParams()
9+
10+
return (
11+
<div data-testid="admin-user-component">
12+
<div data-testid="admin-user-id">{params.userId}</div>
13+
</div>
14+
)
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
3+
export const Route = createFileRoute('/masks/public/$username')({
4+
component: PublicUserRoute,
5+
})
6+
7+
function PublicUserRoute() {
8+
const params = Route.useParams()
9+
10+
return (
11+
<div data-testid="public-user-component">
12+
<div data-testid="public-username">{params.username}</div>
13+
</div>
14+
)
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {
2+
Link,
3+
Outlet,
4+
createFileRoute,
5+
useRouterState,
6+
} from '@tanstack/react-router'
7+
8+
export const Route = createFileRoute('/masks')({
9+
component: MasksLayout,
10+
})
11+
12+
function MasksLayout() {
13+
const location = useRouterState({
14+
select: (state) => state.location,
15+
})
16+
17+
return (
18+
<div>
19+
<h2>Route Masks</h2>
20+
<nav>
21+
<Link
22+
to="/masks/admin/$userId"
23+
params={{ userId: '42' }}
24+
data-testid="link-to-admin-mask"
25+
>
26+
Go to admin user
27+
</Link>
28+
</nav>
29+
<div>
30+
<div data-testid="router-pathname">{location.pathname}</div>
31+
<div data-testid="router-masked-pathname">
32+
{location.maskedLocation?.pathname ?? ''}
33+
</div>
34+
</div>
35+
<Outlet />
36+
</div>
37+
)
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test('route masks transform params and expose masked pathname in the browser (react)', async ({
4+
page,
5+
}) => {
6+
await page.goto('/')
7+
8+
await page.getByTestId('link-to-masks').click()
9+
await expect(page.getByText('Route Masks')).toBeVisible()
10+
11+
const link = page.getByTestId('link-to-admin-mask')
12+
await link.click()
13+
14+
await page.waitForURL('/masks/public/user-42')
15+
16+
await expect(page.getByTestId('admin-user-component')).toBeInViewport()
17+
await expect(page.getByTestId('admin-user-id')).toHaveText('42')
18+
19+
await expect(page.getByTestId('router-pathname')).toHaveText(
20+
'/masks/admin/42',
21+
)
22+
23+
await expect(page.getByTestId('router-masked-pathname')).toHaveText(
24+
'/masks/public/user-42',
25+
)
26+
})

e2e/solid-router/basic-file-based/src/main.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1-
import { RouterProvider, createRouter } from '@tanstack/solid-router'
1+
import {
2+
RouterProvider,
3+
createRouteMask,
4+
createRouter,
5+
} from '@tanstack/solid-router'
26
import { render } from 'solid-js/web'
37
import { routeTree } from './routeTree.gen'
48
import './styles.css'
59

10+
const mask = createRouteMask({
11+
routeTree,
12+
from: '/masks/admin/$userId',
13+
to: '/masks/public/$username',
14+
params: (prev) => ({
15+
username: `user-${prev.userId}`,
16+
}),
17+
})
18+
619
// Set up a Router instance
720
const router = createRouter({
821
routeTree,
922
defaultPreload: 'intent',
1023
defaultStaleTime: 5000,
1124
scrollRestoration: true,
25+
routeMasks: [mask],
1226
})
1327

1428
// Register things for typesafety

0 commit comments

Comments
 (0)