diff --git a/e2e/react-router/basic-file-based/src/components/RenderCounter.tsx b/e2e/react-router/basic-file-based/src/components/RenderCounter.tsx
new file mode 100644
index 00000000000..38eff63cd32
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/components/RenderCounter.tsx
@@ -0,0 +1,7 @@
+import { useRef } from 'react'
+
+export const RenderCounter = () => {
+ const renderCounter = useRef(0)
+ renderCounter.current = renderCounter.current + 1
+ return <>{renderCounter.current}>
+}
diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
index 62af050d6e8..6c4c9159418 100644
--- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts
+++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
@@ -57,21 +57,23 @@ import { Route as ParamsPsWildcardPrefixChar123Char125RouteImport } from './rout
import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$'
import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix'
import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}'
-import { Route as ParamsPsNamedFooRouteImport } from './routes/params-ps/named/$foo'
import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b'
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside'
import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout'
+import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route'
import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index'
import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index'
import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index'
import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index'
import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index'
import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index'
+import { Route as ParamsPsNamedFooBarRouteRouteImport } from './routes/params-ps/named/$foo/$bar.route'
import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index'
import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index'
import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index'
import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index'
+import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz'
const groupRouteImport = createFileRoute('/(group)')()
@@ -321,11 +323,6 @@ const ParamsPsNamedPrefixChar123fooChar125Route =
path: '/params-ps/named/prefix{$foo}',
getParentRoute: () => rootRouteImport,
} as any)
-const ParamsPsNamedFooRoute = ParamsPsNamedFooRouteImport.update({
- id: '/params-ps/named/$foo',
- path: '/params-ps/named/$foo',
- getParentRoute: () => rootRouteImport,
-} as any)
const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({
id: '/layout-b',
path: '/layout-b',
@@ -346,6 +343,11 @@ const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({
path: '/insidelayout',
getParentRoute: () => groupLayoutRoute,
} as any)
+const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({
+ id: '/params-ps/named/$foo',
+ path: '/params-ps/named/$foo',
+ getParentRoute: () => rootRouteImport,
+} as any)
const RelativeUseNavigateWithSearchIndexRoute =
RelativeUseNavigateWithSearchIndexRouteImport.update({
id: '/with-search/',
@@ -380,6 +382,12 @@ const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({
path: '/nested/',
getParentRoute: () => RelativeLinkRouteRoute,
} as any)
+const ParamsPsNamedFooBarRouteRoute =
+ ParamsPsNamedFooBarRouteRouteImport.update({
+ id: '/$bar',
+ path: '/$bar',
+ getParentRoute: () => ParamsPsNamedFooRouteRoute,
+ } as any)
const RelativeUseNavigatePathPathIndexRoute =
RelativeUseNavigatePathPathIndexRouteImport.update({
id: '/path/$path/',
@@ -404,6 +412,11 @@ const RelativeLinkNestedDeepIndexRoute =
path: '/nested/deep/',
getParentRoute: () => RelativeLinkRouteRoute,
} as any)
+const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({
+ id: '/$baz',
+ path: '/$baz',
+ getParentRoute: () => ParamsPsNamedFooBarRouteRoute,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof groupLayoutRouteWithChildren
@@ -430,11 +443,11 @@ export interface FileRoutesByFullPath {
'/redirect': typeof RedirectIndexRoute
'/relative': typeof RelativeIndexRoute
'/search-params/': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/insidelayout': typeof groupLayoutInsidelayoutRoute
'/subfolder/inside': typeof groupSubfolderInsideRoute
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
- '/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -454,12 +467,14 @@ export interface FileRoutesByFullPath {
'/params-ps/named': typeof ParamsPsNamedIndexRoute
'/params-ps/wildcard': typeof ParamsPsWildcardIndexRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested': typeof RelativeLinkNestedIndexRoute
'/relative/link/path': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -487,11 +502,11 @@ export interface FileRoutesByTo {
'/redirect': typeof RedirectIndexRoute
'/relative': typeof RelativeIndexRoute
'/search-params': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/insidelayout': typeof groupLayoutInsidelayoutRoute
'/subfolder/inside': typeof groupSubfolderInsideRoute
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
- '/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -511,12 +526,14 @@ export interface FileRoutesByTo {
'/params-ps/named': typeof ParamsPsNamedIndexRoute
'/params-ps/wildcard': typeof ParamsPsWildcardIndexRoute
'/redirect/$target': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested': typeof RelativeLinkNestedIndexRoute
'/relative/link/path': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -552,11 +569,11 @@ export interface FileRoutesById {
'/redirect/': typeof RedirectIndexRoute
'/relative/': typeof RelativeIndexRoute
'/search-params/': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/(group)/_layout/insidelayout': typeof groupLayoutInsidelayoutRoute
'/(group)/subfolder/inside': typeof groupSubfolderInsideRoute
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
'/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
- '/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
'/params-ps/wildcard/$': typeof ParamsPsWildcardSplatRoute
@@ -576,12 +593,14 @@ export interface FileRoutesById {
'/params-ps/named/': typeof ParamsPsNamedIndexRoute
'/params-ps/wildcard/': typeof ParamsPsWildcardIndexRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested/': typeof RelativeLinkNestedIndexRoute
'/relative/link/path/': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search/': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested/': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path/': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search/': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep/': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path/': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep/': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -614,11 +633,11 @@ export interface FileRouteTypes {
| '/redirect'
| '/relative'
| '/search-params/'
+ | '/params-ps/named/$foo'
| '/insidelayout'
| '/subfolder/inside'
| '/layout-a'
| '/layout-b'
- | '/params-ps/named/$foo'
| '/params-ps/named/prefix{$foo}'
| '/params-ps/named/{$foo}suffix'
| '/params-ps/wildcard/$'
@@ -638,12 +657,14 @@ export interface FileRouteTypes {
| '/params-ps/named'
| '/params-ps/wildcard'
| '/redirect/$target/'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested'
| '/relative/link/path'
| '/relative/link/with-search'
| '/relative/useNavigate/nested'
| '/relative/useNavigate/path'
| '/relative/useNavigate/with-search'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep'
| '/relative/link/path/$path'
| '/relative/useNavigate/nested/deep'
@@ -671,11 +692,11 @@ export interface FileRouteTypes {
| '/redirect'
| '/relative'
| '/search-params'
+ | '/params-ps/named/$foo'
| '/insidelayout'
| '/subfolder/inside'
| '/layout-a'
| '/layout-b'
- | '/params-ps/named/$foo'
| '/params-ps/named/prefix{$foo}'
| '/params-ps/named/{$foo}suffix'
| '/params-ps/wildcard/$'
@@ -695,12 +716,14 @@ export interface FileRouteTypes {
| '/params-ps/named'
| '/params-ps/wildcard'
| '/redirect/$target'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested'
| '/relative/link/path'
| '/relative/link/with-search'
| '/relative/useNavigate/nested'
| '/relative/useNavigate/path'
| '/relative/useNavigate/with-search'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep'
| '/relative/link/path/$path'
| '/relative/useNavigate/nested/deep'
@@ -735,11 +758,11 @@ export interface FileRouteTypes {
| '/redirect/'
| '/relative/'
| '/search-params/'
+ | '/params-ps/named/$foo'
| '/(group)/_layout/insidelayout'
| '/(group)/subfolder/inside'
| '/_layout/_layout-2/layout-a'
| '/_layout/_layout-2/layout-b'
- | '/params-ps/named/$foo'
| '/params-ps/named/prefix{$foo}'
| '/params-ps/named/{$foo}suffix'
| '/params-ps/wildcard/$'
@@ -759,12 +782,14 @@ export interface FileRouteTypes {
| '/params-ps/named/'
| '/params-ps/wildcard/'
| '/redirect/$target/'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested/'
| '/relative/link/path/'
| '/relative/link/with-search/'
| '/relative/useNavigate/nested/'
| '/relative/useNavigate/path/'
| '/relative/useNavigate/with-search/'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep/'
| '/relative/link/path/$path/'
| '/relative/useNavigate/nested/deep/'
@@ -792,7 +817,7 @@ export interface RootRouteChildren {
ParamsPsIndexRoute: typeof ParamsPsIndexRoute
RedirectIndexRoute: typeof RedirectIndexRoute
RelativeIndexRoute: typeof RelativeIndexRoute
- ParamsPsNamedFooRoute: typeof ParamsPsNamedFooRoute
+ ParamsPsNamedFooRouteRoute: typeof ParamsPsNamedFooRouteRouteWithChildren
ParamsPsNamedPrefixChar123fooChar125Route: typeof ParamsPsNamedPrefixChar123fooChar125Route
ParamsPsNamedChar123fooChar125suffixRoute: typeof ParamsPsNamedChar123fooChar125suffixRoute
ParamsPsWildcardSplatRoute: typeof ParamsPsWildcardSplatRoute
@@ -1138,13 +1163,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport
parentRoute: typeof rootRouteImport
}
- '/params-ps/named/$foo': {
- id: '/params-ps/named/$foo'
- path: '/params-ps/named/$foo'
- fullPath: '/params-ps/named/$foo'
- preLoaderRoute: typeof ParamsPsNamedFooRouteImport
- parentRoute: typeof rootRouteImport
- }
'/_layout/_layout-2/layout-b': {
id: '/_layout/_layout-2/layout-b'
path: '/layout-b'
@@ -1173,6 +1191,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport
parentRoute: typeof groupLayoutRoute
}
+ '/params-ps/named/$foo': {
+ id: '/params-ps/named/$foo'
+ path: '/params-ps/named/$foo'
+ fullPath: '/params-ps/named/$foo'
+ preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/relative/useNavigate/with-search/': {
id: '/relative/useNavigate/with-search/'
path: '/with-search'
@@ -1215,6 +1240,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport
parentRoute: typeof RelativeLinkRouteRoute
}
+ '/params-ps/named/$foo/$bar': {
+ id: '/params-ps/named/$foo/$bar'
+ path: '/$bar'
+ fullPath: '/params-ps/named/$foo/$bar'
+ preLoaderRoute: typeof ParamsPsNamedFooBarRouteRouteImport
+ parentRoute: typeof ParamsPsNamedFooRouteRoute
+ }
'/relative/useNavigate/path/$path/': {
id: '/relative/useNavigate/path/$path/'
path: '/path/$path'
@@ -1243,6 +1275,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport
parentRoute: typeof RelativeLinkRouteRoute
}
+ '/params-ps/named/$foo/$bar/$baz': {
+ id: '/params-ps/named/$foo/$bar/$baz'
+ path: '/$baz'
+ fullPath: '/params-ps/named/$foo/$bar/$baz'
+ preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport
+ parentRoute: typeof ParamsPsNamedFooBarRouteRoute
+ }
}
}
@@ -1394,6 +1433,33 @@ const RedirectTargetRouteWithChildren = RedirectTargetRoute._addFileChildren(
RedirectTargetRouteChildren,
)
+interface ParamsPsNamedFooBarRouteRouteChildren {
+ ParamsPsNamedFooBarBazRoute: typeof ParamsPsNamedFooBarBazRoute
+}
+
+const ParamsPsNamedFooBarRouteRouteChildren: ParamsPsNamedFooBarRouteRouteChildren =
+ {
+ ParamsPsNamedFooBarBazRoute: ParamsPsNamedFooBarBazRoute,
+ }
+
+const ParamsPsNamedFooBarRouteRouteWithChildren =
+ ParamsPsNamedFooBarRouteRoute._addFileChildren(
+ ParamsPsNamedFooBarRouteRouteChildren,
+ )
+
+interface ParamsPsNamedFooRouteRouteChildren {
+ ParamsPsNamedFooBarRouteRoute: typeof ParamsPsNamedFooBarRouteRouteWithChildren
+}
+
+const ParamsPsNamedFooRouteRouteChildren: ParamsPsNamedFooRouteRouteChildren = {
+ ParamsPsNamedFooBarRouteRoute: ParamsPsNamedFooBarRouteRouteWithChildren,
+}
+
+const ParamsPsNamedFooRouteRouteWithChildren =
+ ParamsPsNamedFooRouteRoute._addFileChildren(
+ ParamsPsNamedFooRouteRouteChildren,
+ )
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
SearchParamsRouteRoute: SearchParamsRouteRouteWithChildren,
@@ -1416,7 +1482,7 @@ const rootRouteChildren: RootRouteChildren = {
ParamsPsIndexRoute: ParamsPsIndexRoute,
RedirectIndexRoute: RedirectIndexRoute,
RelativeIndexRoute: RelativeIndexRoute,
- ParamsPsNamedFooRoute: ParamsPsNamedFooRoute,
+ ParamsPsNamedFooRouteRoute: ParamsPsNamedFooRouteRouteWithChildren,
ParamsPsNamedPrefixChar123fooChar125Route:
ParamsPsNamedPrefixChar123fooChar125Route,
ParamsPsNamedChar123fooChar125suffixRoute:
diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo.tsx
deleted file mode 100644
index dae4251e293..00000000000
--- a/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { createFileRoute } from '@tanstack/react-router'
-export const Route = createFileRoute('/params-ps/named/$foo')({
- component: RouteComponent,
-})
-
-function RouteComponent() {
- const p = Route.useParams()
- return (
-
-
ParamsNamedFoo
-
{JSON.stringify(p)}
-
- )
-}
diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx
new file mode 100644
index 00000000000..2a38545def6
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/params-ps/named/$foo/$bar/$baz')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const { foo, bar, baz } = Route.useParams()
+ return (
+
+ Hello "/params-ps/named/{foo}/{bar}/{baz}"!
+
+ baz: {baz}
+
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx
new file mode 100644
index 00000000000..653dcd951a2
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx
@@ -0,0 +1,45 @@
+import {
+ Link,
+ Outlet,
+ createFileRoute,
+ useParams,
+} from '@tanstack/react-router'
+import { RenderCounter } from '../../../../components/RenderCounter'
+
+export const Route = createFileRoute('/params-ps/named/$foo/$bar')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const { foo, bar, baz } = useParams({
+ strict: false,
+ })
+
+ return (
+
+ Hello "/params-ps/named/{foo}/{bar}"!
+
+ Bar Render Count:{' '}
+
+
+
+
+
+ Bar: {bar}
+
+
+ Baz in Bar:{' '}
+ {baz ?? 'no param'}
+
+
+ To Baz
+
+
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx
new file mode 100644
index 00000000000..432facc3cc7
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx
@@ -0,0 +1,42 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
+import { RenderCounter } from '../../../../components/RenderCounter'
+
+export const Route = createFileRoute('/params-ps/named/$foo')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const foo = Route.useParams()
+ return (
+
+
ParamsNamedFoo
+
+ RenderCount:{' '}
+
+
+
+
+
{JSON.stringify(foo)}
+
+ Index
+
+
+ Bar1
+
+
+ Bar2
+
+
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/named/index.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/named/index.tsx
index f9a9a7ac06e..e11cbd530cd 100644
--- a/e2e/react-router/basic-file-based/src/routes/params-ps/named/index.tsx
+++ b/e2e/react-router/basic-file-based/src/routes/params-ps/named/index.tsx
@@ -1,5 +1,4 @@
-import { createFileRoute } from '@tanstack/react-router'
-import { redirect } from '@tanstack/react-router'
+import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/params-ps/named/')({
beforeLoad: () => {
diff --git a/e2e/react-router/basic-file-based/tests/params.spec.ts b/e2e/react-router/basic-file-based/tests/params.spec.ts
index 546fa80d31e..59dc844fd81 100644
--- a/e2e/react-router/basic-file-based/tests/params.spec.ts
+++ b/e2e/react-router/basic-file-based/tests/params.spec.ts
@@ -129,6 +129,80 @@ test.describe('params operations + prefix/suffix', () => {
expect(paramsObj).toEqual(params)
})
})
+
+ test(`ensure use params doesn't cause excess renders and is stable across various usage options`, async ({
+ page,
+ }) => {
+ await page.goto('/params-ps/named/foo')
+ await page.waitForLoadState('networkidle')
+
+ const pagePathname = new URL(page.url()).pathname
+ expect(pagePathname).toBe('/params-ps/named/foo')
+
+ const fooRenderCount = page.getByTestId('foo-render-count')
+ const fooIndexLink = page.getByTestId('params-foo-links-index')
+ const fooBar1Link = page.getByTestId('params-foo-links-bar1')
+ const fooBar2Link = page.getByTestId('params-foo-links-bar2')
+ const fooBarBazLink = page.getByTestId('params-foo-bar-links-baz')
+ const fooValue = page.getByTestId('params-output')
+ const fooBarValue = page.getByTestId('foo-bar-value')
+ const fooBazInBarValue = page.getByTestId('foo-baz-in-bar-value')
+ const fooBarRenderCount = page.getByTestId('foo-bar-render-count')
+ const fooBarBazValue = page.getByTestId('foo-bar-baz-value')
+
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooValue).toBeInViewport()
+ await expect(fooIndexLink).toBeInViewport()
+ await expect(fooBar1Link).toBeInViewport()
+ await expect(fooBar2Link).toBeInViewport()
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+
+ await fooBar1Link.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooBazInBarValue).toBeInViewport()
+ await expect(fooBarBazLink).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarRenderCount).toHaveText('1')
+ await expect(fooBarValue).toHaveText('1')
+ await expect(fooBazInBarValue).toHaveText('no param')
+
+ await fooBarBazLink.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooBazInBarValue).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarRenderCount).toHaveText('2')
+ await expect(fooBarValue).toHaveText('1')
+ await expect(fooBazInBarValue).toHaveText('1_10')
+ await expect(fooBarBazValue).toHaveText('1_10')
+
+ await fooBar2Link.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarValue).toHaveText('2')
+
+ await fooIndexLink.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarValue).not.toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ })
})
test.describe('wildcard param', () => {
diff --git a/e2e/solid-router/basic-file-based/src/routeTree.gen.ts b/e2e/solid-router/basic-file-based/src/routeTree.gen.ts
index b81fbe69d5d..1fa808525cb 100644
--- a/e2e/solid-router/basic-file-based/src/routeTree.gen.ts
+++ b/e2e/solid-router/basic-file-based/src/routeTree.gen.ts
@@ -51,16 +51,19 @@ import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layo
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside'
import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout'
+import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route'
import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index'
import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index'
import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index'
import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index'
import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index'
import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index'
+import { Route as ParamsPsNamedFooBarRouteRouteImport } from './routes/params-ps/named/$foo/$bar.route'
import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index'
import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index'
import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index'
import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index'
+import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz'
const groupRouteImport = createFileRoute('/(group)')()
@@ -274,6 +277,11 @@ const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({
path: '/insidelayout',
getParentRoute: () => groupLayoutRoute,
} as any)
+const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({
+ id: '/params-ps/named/$foo',
+ path: '/params-ps/named/$foo',
+ getParentRoute: () => rootRouteImport,
+} as any)
const RelativeUseNavigateWithSearchIndexRoute =
RelativeUseNavigateWithSearchIndexRouteImport.update({
id: '/with-search/',
@@ -308,6 +316,12 @@ const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({
path: '/nested/',
getParentRoute: () => RelativeLinkRouteRoute,
} as any)
+const ParamsPsNamedFooBarRouteRoute =
+ ParamsPsNamedFooBarRouteRouteImport.update({
+ id: '/$bar',
+ path: '/$bar',
+ getParentRoute: () => ParamsPsNamedFooRouteRoute,
+ } as any)
const RelativeUseNavigatePathPathIndexRoute =
RelativeUseNavigatePathPathIndexRouteImport.update({
id: '/path/$path/',
@@ -332,6 +346,11 @@ const RelativeLinkNestedDeepIndexRoute =
path: '/nested/deep/',
getParentRoute: () => RelativeLinkRouteRoute,
} as any)
+const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({
+ id: '/$baz',
+ path: '/$baz',
+ getParentRoute: () => ParamsPsNamedFooBarRouteRoute,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof groupLayoutRouteWithChildren
@@ -355,6 +374,7 @@ export interface FileRoutesByFullPath {
'/redirect': typeof RedirectIndexRoute
'/relative': typeof RelativeIndexRoute
'/search-params/': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/insidelayout': typeof groupLayoutInsidelayoutRoute
'/subfolder/inside': typeof groupSubfolderInsideRoute
'/layout-a': typeof LayoutLayout2LayoutARoute
@@ -371,12 +391,14 @@ export interface FileRoutesByFullPath {
'/relative/useNavigate/relative-useNavigate-a': typeof RelativeUseNavigateRelativeUseNavigateARoute
'/relative/useNavigate/relative-useNavigate-b': typeof RelativeUseNavigateRelativeUseNavigateBRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested': typeof RelativeLinkNestedIndexRoute
'/relative/link/path': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -401,6 +423,7 @@ export interface FileRoutesByTo {
'/redirect': typeof RedirectIndexRoute
'/relative': typeof RelativeIndexRoute
'/search-params': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/insidelayout': typeof groupLayoutInsidelayoutRoute
'/subfolder/inside': typeof groupSubfolderInsideRoute
'/layout-a': typeof LayoutLayout2LayoutARoute
@@ -417,12 +440,14 @@ export interface FileRoutesByTo {
'/relative/useNavigate/relative-useNavigate-a': typeof RelativeUseNavigateRelativeUseNavigateARoute
'/relative/useNavigate/relative-useNavigate-b': typeof RelativeUseNavigateRelativeUseNavigateBRoute
'/redirect/$target': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested': typeof RelativeLinkNestedIndexRoute
'/relative/link/path': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -455,6 +480,7 @@ export interface FileRoutesById {
'/redirect/': typeof RedirectIndexRoute
'/relative/': typeof RelativeIndexRoute
'/search-params/': typeof SearchParamsIndexRoute
+ '/params-ps/named/$foo': typeof ParamsPsNamedFooRouteRouteWithChildren
'/(group)/_layout/insidelayout': typeof groupLayoutInsidelayoutRoute
'/(group)/subfolder/inside': typeof groupSubfolderInsideRoute
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
@@ -471,12 +497,14 @@ export interface FileRoutesById {
'/relative/useNavigate/relative-useNavigate-a': typeof RelativeUseNavigateRelativeUseNavigateARoute
'/relative/useNavigate/relative-useNavigate-b': typeof RelativeUseNavigateRelativeUseNavigateBRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/params-ps/named/$foo/$bar': typeof ParamsPsNamedFooBarRouteRouteWithChildren
'/relative/link/nested/': typeof RelativeLinkNestedIndexRoute
'/relative/link/path/': typeof RelativeLinkPathIndexRoute
'/relative/link/with-search/': typeof RelativeLinkWithSearchIndexRoute
'/relative/useNavigate/nested/': typeof RelativeUseNavigateNestedIndexRoute
'/relative/useNavigate/path/': typeof RelativeUseNavigatePathIndexRoute
'/relative/useNavigate/with-search/': typeof RelativeUseNavigateWithSearchIndexRoute
+ '/params-ps/named/$foo/$bar/$baz': typeof ParamsPsNamedFooBarBazRoute
'/relative/link/nested/deep/': typeof RelativeLinkNestedDeepIndexRoute
'/relative/link/path/$path/': typeof RelativeLinkPathPathIndexRoute
'/relative/useNavigate/nested/deep/': typeof RelativeUseNavigateNestedDeepIndexRoute
@@ -506,6 +534,7 @@ export interface FileRouteTypes {
| '/redirect'
| '/relative'
| '/search-params/'
+ | '/params-ps/named/$foo'
| '/insidelayout'
| '/subfolder/inside'
| '/layout-a'
@@ -522,12 +551,14 @@ export interface FileRouteTypes {
| '/relative/useNavigate/relative-useNavigate-a'
| '/relative/useNavigate/relative-useNavigate-b'
| '/redirect/$target/'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested'
| '/relative/link/path'
| '/relative/link/with-search'
| '/relative/useNavigate/nested'
| '/relative/useNavigate/path'
| '/relative/useNavigate/with-search'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep'
| '/relative/link/path/$path'
| '/relative/useNavigate/nested/deep'
@@ -552,6 +583,7 @@ export interface FileRouteTypes {
| '/redirect'
| '/relative'
| '/search-params'
+ | '/params-ps/named/$foo'
| '/insidelayout'
| '/subfolder/inside'
| '/layout-a'
@@ -568,12 +600,14 @@ export interface FileRouteTypes {
| '/relative/useNavigate/relative-useNavigate-a'
| '/relative/useNavigate/relative-useNavigate-b'
| '/redirect/$target'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested'
| '/relative/link/path'
| '/relative/link/with-search'
| '/relative/useNavigate/nested'
| '/relative/useNavigate/path'
| '/relative/useNavigate/with-search'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep'
| '/relative/link/path/$path'
| '/relative/useNavigate/nested/deep'
@@ -605,6 +639,7 @@ export interface FileRouteTypes {
| '/redirect/'
| '/relative/'
| '/search-params/'
+ | '/params-ps/named/$foo'
| '/(group)/_layout/insidelayout'
| '/(group)/subfolder/inside'
| '/_layout/_layout-2/layout-a'
@@ -621,12 +656,14 @@ export interface FileRouteTypes {
| '/relative/useNavigate/relative-useNavigate-a'
| '/relative/useNavigate/relative-useNavigate-b'
| '/redirect/$target/'
+ | '/params-ps/named/$foo/$bar'
| '/relative/link/nested/'
| '/relative/link/path/'
| '/relative/link/with-search/'
| '/relative/useNavigate/nested/'
| '/relative/useNavigate/path/'
| '/relative/useNavigate/with-search/'
+ | '/params-ps/named/$foo/$bar/$baz'
| '/relative/link/nested/deep/'
| '/relative/link/path/$path/'
| '/relative/useNavigate/nested/deep/'
@@ -651,6 +688,7 @@ export interface RootRouteChildren {
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
RedirectIndexRoute: typeof RedirectIndexRoute
RelativeIndexRoute: typeof RelativeIndexRoute
+ ParamsPsNamedFooRouteRoute: typeof ParamsPsNamedFooRouteRouteWithChildren
ParamsSingleValueRoute: typeof ParamsSingleValueRoute
PostsPostIdEditRoute: typeof PostsPostIdEditRoute
RedirectPreloadFirstRoute: typeof RedirectPreloadFirstRoute
@@ -947,6 +985,13 @@ declare module '@tanstack/solid-router' {
preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport
parentRoute: typeof groupLayoutRoute
}
+ '/params-ps/named/$foo': {
+ id: '/params-ps/named/$foo'
+ path: '/params-ps/named/$foo'
+ fullPath: '/params-ps/named/$foo'
+ preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/relative/useNavigate/with-search/': {
id: '/relative/useNavigate/with-search/'
path: '/with-search'
@@ -989,6 +1034,13 @@ declare module '@tanstack/solid-router' {
preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport
parentRoute: typeof RelativeLinkRouteRoute
}
+ '/params-ps/named/$foo/$bar': {
+ id: '/params-ps/named/$foo/$bar'
+ path: '/$bar'
+ fullPath: '/params-ps/named/$foo/$bar'
+ preLoaderRoute: typeof ParamsPsNamedFooBarRouteRouteImport
+ parentRoute: typeof ParamsPsNamedFooRouteRoute
+ }
'/relative/useNavigate/path/$path/': {
id: '/relative/useNavigate/path/$path/'
path: '/path/$path'
@@ -1017,6 +1069,13 @@ declare module '@tanstack/solid-router' {
preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport
parentRoute: typeof RelativeLinkRouteRoute
}
+ '/params-ps/named/$foo/$bar/$baz': {
+ id: '/params-ps/named/$foo/$bar/$baz'
+ path: '/$baz'
+ fullPath: '/params-ps/named/$foo/$bar/$baz'
+ preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport
+ parentRoute: typeof ParamsPsNamedFooBarRouteRoute
+ }
}
}
@@ -1168,6 +1227,33 @@ const RedirectTargetRouteWithChildren = RedirectTargetRoute._addFileChildren(
RedirectTargetRouteChildren,
)
+interface ParamsPsNamedFooBarRouteRouteChildren {
+ ParamsPsNamedFooBarBazRoute: typeof ParamsPsNamedFooBarBazRoute
+}
+
+const ParamsPsNamedFooBarRouteRouteChildren: ParamsPsNamedFooBarRouteRouteChildren =
+ {
+ ParamsPsNamedFooBarBazRoute: ParamsPsNamedFooBarBazRoute,
+ }
+
+const ParamsPsNamedFooBarRouteRouteWithChildren =
+ ParamsPsNamedFooBarRouteRoute._addFileChildren(
+ ParamsPsNamedFooBarRouteRouteChildren,
+ )
+
+interface ParamsPsNamedFooRouteRouteChildren {
+ ParamsPsNamedFooBarRouteRoute: typeof ParamsPsNamedFooBarRouteRouteWithChildren
+}
+
+const ParamsPsNamedFooRouteRouteChildren: ParamsPsNamedFooRouteRouteChildren = {
+ ParamsPsNamedFooBarRouteRoute: ParamsPsNamedFooBarRouteRouteWithChildren,
+}
+
+const ParamsPsNamedFooRouteRouteWithChildren =
+ ParamsPsNamedFooRouteRoute._addFileChildren(
+ ParamsPsNamedFooRouteRouteChildren,
+ )
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
SearchParamsRouteRoute: SearchParamsRouteRouteWithChildren,
@@ -1186,6 +1272,7 @@ const rootRouteChildren: RootRouteChildren = {
RedirectTargetRoute: RedirectTargetRouteWithChildren,
RedirectIndexRoute: RedirectIndexRoute,
RelativeIndexRoute: RelativeIndexRoute,
+ ParamsPsNamedFooRouteRoute: ParamsPsNamedFooRouteRouteWithChildren,
ParamsSingleValueRoute: ParamsSingleValueRoute,
PostsPostIdEditRoute: PostsPostIdEditRoute,
RedirectPreloadFirstRoute: RedirectPreloadFirstRoute,
diff --git a/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx
new file mode 100644
index 00000000000..0b6488f389e
--- /dev/null
+++ b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/params-ps/named/$foo/$bar/$baz')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const params = Route.useParams()
+ return (
+
+ Hello "/params-ps/named/$foo/$bar/$baz"!
+
+ baz: {params().baz}
+
+
+ )
+}
diff --git a/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx
new file mode 100644
index 00000000000..4387aed56d5
--- /dev/null
+++ b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx
@@ -0,0 +1,64 @@
+import {
+ Link,
+ Outlet,
+ createFileRoute,
+ useParams,
+} from '@tanstack/solid-router'
+import { createEffect, createSignal } from 'solid-js'
+
+export const Route = createFileRoute('/params-ps/named/$foo/$bar')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const [renderBarCount, setBarRenderCount] = createSignal(0)
+ const [renderBazCount, setBazRenderCount] = createSignal(0)
+
+ const params = useParams({
+ strict: false,
+ })
+
+ createEffect(() => {
+ params().bar
+ setBarRenderCount((prev) => prev + 1)
+ })
+
+ createEffect(() => {
+ params().baz
+ setBazRenderCount((prev) => prev + 1)
+ })
+
+ return (
+
+ Hello "/params-ps/named/$foo/$bar"!
+
+ Bar Render Count:{' '}
+ {renderBarCount()}
+
+
+ Bar: {params().bar}
+
+
+ Baz in Bar Render Count:{' '}
+
+ {renderBazCount()}
+
+
+
+ Baz in Bar:{' '}
+
+ {params().baz ?? 'no param'}
+
+
+
+ To Baz
+
+
+
+ )
+}
diff --git a/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx
new file mode 100644
index 00000000000..58a78e38223
--- /dev/null
+++ b/e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx
@@ -0,0 +1,47 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/solid-router'
+import { createEffect, createSignal } from 'solid-js'
+
+export const Route = createFileRoute('/params-ps/named/$foo')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const [renderCount, setRenderCount] = createSignal(0)
+ const params = Route.useParams()
+
+ createEffect(() => {
+ params().foo
+
+ setRenderCount((prev) => prev + 1)
+ })
+
+ return (
+
+
ParamsNamedFoo
+
+ RenderCount: {renderCount()}
+
+
{JSON.stringify(params())}
+
+ Index
+
+
+ Bar1
+
+
+ Bar2
+
+
+
+ )
+}
diff --git a/e2e/solid-router/basic-file-based/tests/params.spec.ts b/e2e/solid-router/basic-file-based/tests/params.spec.ts
index e95bc56c598..42930fc41a1 100644
--- a/e2e/solid-router/basic-file-based/tests/params.spec.ts
+++ b/e2e/solid-router/basic-file-based/tests/params.spec.ts
@@ -54,3 +54,78 @@ test.describe('ensure single params have been parsed correctly whilst being stab
})
}
})
+
+test('ensure only applicable params are returned and updated with multiple params', async ({
+ page,
+}) => {
+ await page.goto('/params-ps/named/foo')
+ await page.waitForLoadState('networkidle')
+
+ const pagePathname = new URL(page.url()).pathname
+ expect(pagePathname).toBe('/params-ps/named/foo')
+
+ const fooRenderCount = page.getByTestId('foo-render-count')
+ const fooIndexLink = page.getByTestId('params-foo-links-index')
+ const fooBar1Link = page.getByTestId('params-foo-links-bar1')
+ const fooBar2Link = page.getByTestId('params-foo-links-bar2')
+ const fooBarBazLink = page.getByTestId('params-foo-bar-links-baz')
+ const fooValue = page.getByTestId('params-output')
+ const fooBarValue = page.getByTestId('foo-bar-value')
+ const fooBazInBarValue = page.getByTestId('foo-baz-in-bar-value')
+ const fooBarRenderCount = page.getByTestId('foo-bar-render-count')
+ const fooBazInBarRenderCount = page.getByTestId('foo-baz-in-bar-render-count')
+ const fooBarBazValue = page.getByTestId('foo-bar-baz-value')
+
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooValue).toBeInViewport()
+ await expect(fooIndexLink).toBeInViewport()
+ await expect(fooBar1Link).toBeInViewport()
+ await expect(fooBar2Link).toBeInViewport()
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+
+ await fooBar1Link.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooBazInBarValue).toBeInViewport()
+ await expect(fooBarBazLink).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarRenderCount).toHaveText('1')
+ await expect(fooBarValue).toHaveText('1')
+ await expect(fooBazInBarValue).toHaveText('no param')
+ await expect(fooBazInBarRenderCount).toHaveText('1')
+
+ await fooBarBazLink.click()
+ await page.waitForLoadState('networkidle')
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooBazInBarValue).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarRenderCount).toHaveText('1')
+ await expect(fooBarValue).toHaveText('1')
+ await expect(fooBazInBarValue).toHaveText('1_10')
+ await expect(fooBarBazValue).toHaveText('1_10')
+ await expect(fooBazInBarRenderCount).toHaveText('2')
+
+ await fooBar2Link.click()
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarValue).toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+ await expect(fooBarValue).toHaveText('2')
+
+ await fooIndexLink.click()
+ await expect(fooValue).toBeInViewport()
+ await expect(fooRenderCount).toBeInViewport()
+ await expect(fooBarValue).not.toBeInViewport()
+ await expect(fooValue).toHaveText(JSON.stringify({ foo: 'foo' }))
+ await expect(fooRenderCount).toHaveText('1')
+})
diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx
index 59fbd5bfbb0..7ff3d3934db 100644
--- a/packages/react-router/src/useParams.tsx
+++ b/packages/react-router/src/useParams.tsx
@@ -83,11 +83,13 @@ export function useParams<
> {
return useMatch({
from: opts.from!,
- strict: opts.strict,
shouldThrow: opts.shouldThrow,
structuralSharing: opts.structuralSharing,
- select: (match: any) => {
- return opts.select ? opts.select(match.params) : match.params
+ strict: opts.strict,
+ select: (match) => {
+ const params = opts.strict === false ? match.params : match._strictParams
+
+ return opts.select ? opts.select(params) : params
},
}) as any
}
diff --git a/packages/react-router/tests/optional-path-params.test.tsx b/packages/react-router/tests/optional-path-params.test.tsx
index 23dcf5ab626..e5df289c825 100644
--- a/packages/react-router/tests/optional-path-params.test.tsx
+++ b/packages/react-router/tests/optional-path-params.test.tsx
@@ -781,7 +781,7 @@ describe('React Router - Optional Path Parameters', () => {
getParentRoute: () => postsRoute,
path: '/{-$slug}',
component: () => {
- const { slug } = postsRoute.useParams()
+ const { slug } = postRoute.useParams()
return (
Post Detail
diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx
index 594a271eaa4..a22dcc22e58 100644
--- a/packages/react-router/tests/router.test.tsx
+++ b/packages/react-router/tests/router.test.tsx
@@ -1,4 +1,4 @@
-import { act, useEffect } from 'react'
+import { act, useEffect, useRef } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
cleanup,
@@ -885,12 +885,16 @@ describe('router rendering stability', () => {
remountDeps: {
default?: RemountDepsFn
fooId?: RemountDepsFn
+ foo2Id?: RemountDepsFn
barId?: RemountDepsFn
+ bar2Id?: RemountDepsFn
}
}) {
const mountMocks = {
fooId: vi.fn(),
+ foo2Id: vi.fn(),
barId: vi.fn(),
+ bar2Id: vi.fn(),
}
const rootRoute = createRootRoute({
@@ -927,12 +931,34 @@ describe('router rendering stability', () => {
>
Foo3-Bar2
+
+ Foo2-1-Bar2_1
+
+
+ Foo2-1-Bar2_2
+
+
+ Foo2-2-Bar2_1
+
)
},
})
+
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
@@ -940,6 +966,7 @@ describe('router rendering stability', () => {
return ''
},
})
+
const fooIdRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/foo/$fooId',
@@ -968,7 +995,7 @@ describe('router rendering stability', () => {
})
function BarIdRouteComponent() {
- const barId = fooIdRoute.useParams({ select: (s) => s.barId })
+ const barId = barIdRoute.useParams({ select: (s) => s.barId })
useEffect(() => {
mountMocks.barId()
@@ -981,8 +1008,57 @@ describe('router rendering stability', () => {
)
}
+ const foo2IdRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/foo2/$foo2Id',
+ component: Foo2IdRouteComponent,
+ remountDeps: opts?.remountDeps.foo2Id,
+ })
+
+ function Foo2IdRouteComponent() {
+ const renderCounter = useRef(0)
+ renderCounter.current = renderCounter.current + 1
+
+ const { foo2Id } = foo2IdRoute.useParams()
+
+ useEffect(() => {
+ mountMocks.foo2Id()
+ }, [foo2Id])
+
+ return (
+
+ RenderCount:{' '}
+ {renderCounter.current}
+ Foo page {foo2Id}
+
+
+ )
+ }
+
+ const bar2IdRoute = createRoute({
+ getParentRoute: () => foo2IdRoute,
+ path: '/bar2/$bar2Id',
+ component: Bar2IdRouteComponent,
+ remountDeps: opts?.remountDeps.bar2Id,
+ })
+
+ function Bar2IdRouteComponent() {
+ const { bar2Id } = bar2IdRoute.useParams()
+
+ useEffect(() => {
+ mountMocks.bar2Id()
+ }, [bar2Id])
+
+ return (
+
+ Bar2 page {bar2Id}
+
+ )
+ }
+
const routeTree = rootRoute.addChildren([
fooIdRoute.addChildren([barIdRoute]),
+ foo2IdRoute.addChildren([bar2IdRoute]),
indexRoute,
])
const router = createRouter({
@@ -998,19 +1074,37 @@ describe('router rendering stability', () => {
const foo3bar1 = await screen.findByTestId('link-foo-3-bar-1')
const foo3bar2 = await screen.findByTestId('link-foo-3-bar-2')
+ const foo_2_1_bar2_1 = await screen.findByTestId('link-foo2-1-bar2-1')
+ const foo_2_1_bar2_2 = await screen.findByTestId('link-foo2-1-bar2-2')
+ const foo_2_2_bar2_1 = await screen.findByTestId('link-foo2-2-bar2-1')
expect(foo1).toBeInTheDocument()
expect(foo2).toBeInTheDocument()
expect(foo3bar1).toBeInTheDocument()
expect(foo3bar2).toBeInTheDocument()
-
- return { router, mountMocks, links: { foo1, foo2, foo3bar1, foo3bar2 } }
+ expect(foo_2_1_bar2_1).toBeInTheDocument()
+ expect(foo_2_1_bar2_2).toBeInTheDocument()
+ expect(foo_2_2_bar2_1).toBeInTheDocument()
+
+ return {
+ router,
+ mountMocks,
+ links: {
+ foo1,
+ foo2,
+ foo3bar1,
+ foo3bar2,
+ foo_2_1_bar2_1,
+ foo_2_1_bar2_2,
+ foo_2_2_bar2_1,
+ },
+ }
}
async function check(
- page: 'fooId' | 'barId',
+ page: 'fooId' | 'foo2Id' | 'barId' | 'bar2Id',
expected: { value: string; mountCount: number },
- mountMocks: Record<'fooId' | 'barId', () => void>,
+ mountMocks: Record<'fooId' | 'foo2Id' | 'barId' | 'bar2Id', () => void>,
) {
const p = await screen.findByTestId(`${page}-page`)
expect(p).toBeInTheDocument()
@@ -1027,7 +1121,6 @@ describe('router rendering stability', () => {
await act(() => fireEvent.click(links.foo1))
await check('fooId', { value: '1', mountCount: 1 }, mountMocks)
expect(mountMocks.barId).not.toHaveBeenCalled()
-
await act(() => fireEvent.click(links.foo2))
await check('fooId', { value: '2', mountCount: 1 }, mountMocks)
expect(mountMocks.barId).not.toHaveBeenCalled()
@@ -1035,9 +1128,35 @@ describe('router rendering stability', () => {
await act(() => fireEvent.click(links.foo3bar1))
await check('fooId', { value: '3', mountCount: 1 }, mountMocks)
await check('barId', { value: '1', mountCount: 1 }, mountMocks)
+
await act(() => fireEvent.click(links.foo3bar2))
await check('fooId', { value: '3', mountCount: 1 }, mountMocks)
await check('barId', { value: '2', mountCount: 1 }, mountMocks)
+
+ mountMocks.foo2Id.mockClear()
+ mountMocks.bar2Id.mockClear()
+ await act(() => fireEvent.click(links.foo_2_1_bar2_1))
+ const renderCount = await screen.findByTestId('foo2Id-Render-Count')
+
+ await check('foo2Id', { value: '1', mountCount: 1 }, mountMocks)
+ await check('bar2Id', { value: '1', mountCount: 1 }, mountMocks)
+ expect(renderCount).toBeInTheDocument()
+ expect(renderCount).toHaveTextContent('1')
+
+ await act(() => fireEvent.click(links.foo_2_1_bar2_2))
+ await check('foo2Id', { value: '1', mountCount: 1 }, mountMocks)
+ await check('bar2Id', { value: '2', mountCount: 2 }, mountMocks)
+ expect(renderCount).toBeInTheDocument()
+ expect(renderCount).toHaveTextContent('1')
+
+ mountMocks.foo2Id.mockClear()
+ mountMocks.bar2Id.mockClear()
+
+ await act(() => fireEvent.click(links.foo_2_2_bar2_1))
+ await check('foo2Id', { value: '2', mountCount: 1 }, mountMocks)
+ await check('bar2Id', { value: '1', mountCount: 1 }, mountMocks)
+ expect(renderCount).toBeInTheDocument()
+ expect(renderCount).toHaveTextContent('2')
})
it('should remount the fooId and barId page component when navigating to the same route but different path param if defaultRemountDeps with params is used', async () => {
diff --git a/packages/router-core/src/path.ts b/packages/router-core/src/path.ts
index 827f17c2432..7402001cdf4 100644
--- a/packages/router-core/src/path.ts
+++ b/packages/router-core/src/path.ts
@@ -403,6 +403,10 @@ export function interpolatePath({
if (segment.type === SEGMENT_TYPE_WILDCARD) {
usedParams._splat = params._splat
+
+ // TODO: Deprecate *
+ usedParams['*'] = params._splat
+
const segmentPrefix = segment.prefixSegment || ''
const segmentSuffix = segment.suffixSegment || ''
diff --git a/packages/solid-router/src/useParams.tsx b/packages/solid-router/src/useParams.tsx
index ea00dc62309..1788f69fe94 100644
--- a/packages/solid-router/src/useParams.tsx
+++ b/packages/solid-router/src/useParams.tsx
@@ -62,10 +62,12 @@ export function useParams<
> {
return useMatch({
from: opts.from!,
- strict: opts.strict,
shouldThrow: opts.shouldThrow,
- select: (match: any) => {
- return opts.select ? opts.select(match.params) : match.params
+ strict: opts.strict,
+ select: (match) => {
+ const params = opts.strict === false ? match.params : match._strictParams
+
+ return opts.select ? opts.select(params) : params
},
- } as any) as any
+ }) as Accessor
}