From c3e2ae085b88195b8afc5967fd1fb42b1f0c8fd1 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 13:10:03 +0200 Subject: [PATCH 1/7] update react-router --- packages/react-router/src/useParams.tsx | 21 +++- packages/react-router/tests/router.test.tsx | 133 ++++++++++++++++++-- 2 files changed, 141 insertions(+), 13 deletions(-) diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx index 59fbd5bfbb0..91ca1f0702d 100644 --- a/packages/react-router/src/useParams.tsx +++ b/packages/react-router/src/useParams.tsx @@ -1,4 +1,5 @@ import { useMatch } from './useMatch' +import { useRouter } from './useRouter' import type { StructuralSharingOption, ValidateSelected, @@ -81,13 +82,21 @@ export function useParams< UseParamsResult, TThrow > { - return useMatch({ + const router = useRouter() + + const isStrict = opts.strict !== false + + const matchResult = useMatch({ from: opts.from!, - strict: opts.strict, - shouldThrow: opts.shouldThrow, + shouldThrow: false, structuralSharing: opts.structuralSharing, - select: (match: any) => { - return opts.select ? opts.select(match.params) : match.params - }, + strict: opts.strict, + select: (match) => (isStrict ? match.id : match), }) as any + + const params = isStrict + ? router.getMatch(matchResult)!.params + : matchResult.params + + return opts.select ? (opts.select(params) as any) : (params ?? {}) } diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index 594a271eaa4..8a2dd7b44e2 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_2 + ) }, }) + 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.fooId, + }) + + 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.barId, + }) + + 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 () => { From 02f60535656ecf639932b1718f7c77d222e19559 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 13:10:18 +0200 Subject: [PATCH 2/7] update solid-router --- packages/solid-router/src/useParams.tsx | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/solid-router/src/useParams.tsx b/packages/solid-router/src/useParams.tsx index ea00dc62309..8d4543b81f4 100644 --- a/packages/solid-router/src/useParams.tsx +++ b/packages/solid-router/src/useParams.tsx @@ -1,4 +1,6 @@ +import { createMemo } from 'solid-js' import { useMatch } from './useMatch' +import { useRouter } from './useRouter' import type { Accessor } from 'solid-js' import type { AnyRouter, @@ -60,12 +62,22 @@ export function useParams< ): Accessor< ThrowOrOptional, TThrow> > { - return useMatch({ + const router = useRouter() + + const isStrict = opts.strict !== false + + const matchResult = useMatch({ from: opts.from!, - strict: opts.strict, shouldThrow: opts.shouldThrow, - select: (match: any) => { - return opts.select ? opts.select(match.params) : match.params - }, - } as any) as any + strict: opts.strict, + select: (match) => (isStrict ? match.id : match), + }) as Accessor + + const params = isStrict + ? router.getMatch(matchResult())!.params + : matchResult().params + + return createMemo(() => + opts.select ? (opts.select(params) as any) : (params ?? {}), + ) } From d92cc1bd85b70a79afe1d84b5bf5f815a7824cfc Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 13:11:18 +0200 Subject: [PATCH 3/7] add e2e tests for react-router --- .../src/components/RenderCounter.tsx | 7 ++ .../basic-file-based/src/routeTree.gen.ts | 108 ++++++++++++++---- .../src/routes/params-ps/named/$foo.tsx | 14 --- .../routes/params-ps/named/$foo/$bar.$baz.tsx | 17 +++ .../params-ps/named/$foo/$bar.route.tsx | 45 ++++++++ .../src/routes/params-ps/named/$foo/route.tsx | 42 +++++++ .../src/routes/params-ps/named/index.tsx | 3 +- .../basic-file-based/tests/params.spec.ts | 74 ++++++++++++ 8 files changed, 273 insertions(+), 37 deletions(-) create mode 100644 e2e/react-router/basic-file-based/src/components/RenderCounter.tsx delete mode 100644 e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo.tsx create mode 100644 e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx create mode 100644 e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx create mode 100644 e2e/react-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx 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', () => { From ca593c34e10537ac316e4ce5f4beb9444361316f Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 13:11:35 +0200 Subject: [PATCH 4/7] add e2e tests for solid-router --- .../basic-file-based/src/routeTree.gen.ts | 87 +++++++++++++++++++ .../routes/params-ps/named/$foo/$bar.$baz.tsx | 17 ++++ .../params-ps/named/$foo/$bar.route.tsx | 64 ++++++++++++++ .../src/routes/params-ps/named/$foo/route.tsx | 47 ++++++++++ .../basic-file-based/tests/params.spec.ts | 75 ++++++++++++++++ 5 files changed, 290 insertions(+) create mode 100644 e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.$baz.tsx create mode 100644 e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/$bar.route.tsx create mode 100644 e2e/solid-router/basic-file-based/src/routes/params-ps/named/$foo/route.tsx 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..b60adb27967 --- /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/{params().foo}/{params().bar}/{params().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..890c389c862 --- /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, setBarRenderCountCount] = createSignal(0) + const [renderBazCount, setBazRenderCountCount] = createSignal(0) + + const params = useParams({ + strict: false, + }) + + createEffect(() => { + params().bar + setBarRenderCountCount((prev) => prev + 1) + }) + + createEffect(() => { + params().baz + setBazRenderCountCount((prev) => prev + 1) + }) + + return ( +
+ Hello "/params-ps/named/{params().foo}/{params().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..92122a0f970 --- /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, setRenderCountCount] = createSignal(0) + const params = Route.useParams() + + createEffect(() => { + params().foo + + setRenderCountCount((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') +}) From 5760e773739a16116b6bb46190e4492ea1c69680 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 14:56:26 +0200 Subject: [PATCH 5/7] apply code-rabbit suggestions --- .../routes/params-ps/named/$foo/$bar.$baz.tsx | 2 +- .../routes/params-ps/named/$foo/$bar.route.tsx | 2 +- packages/react-router/src/useParams.tsx | 2 +- packages/react-router/tests/router.test.tsx | 4 ++-- packages/solid-router/src/useParams.tsx | 16 ++++++++++------ 5 files changed, 15 insertions(+), 11 deletions(-) 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 index b60adb27967..0b6488f389e 100644 --- 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 @@ -8,7 +8,7 @@ function RouteComponent() { const params = Route.useParams() return (
- Hello "/params-ps/named/{params().foo}/{params().bar}/{params().baz}"! + 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 index 890c389c862..85006de85ef 100644 --- 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 @@ -30,7 +30,7 @@ function RouteComponent() { return (
- Hello "/params-ps/named/{params().foo}/{params().bar}"! + Hello "/params-ps/named/$foo/$bar"!
Bar Render Count:{' '} {renderBarCount()} diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx index 91ca1f0702d..f06cd8fa6de 100644 --- a/packages/react-router/src/useParams.tsx +++ b/packages/react-router/src/useParams.tsx @@ -95,7 +95,7 @@ export function useParams< }) as any const params = isStrict - ? router.getMatch(matchResult)!.params + ? router.getMatch(matchResult)?.params : matchResult.params return opts.select ? (opts.select(params) as any) : (params ?? {}) diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index 8a2dd7b44e2..ae40d8d8020 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -1012,7 +1012,7 @@ describe('router rendering stability', () => { getParentRoute: () => rootRoute, path: '/foo2/$foo2Id', component: Foo2IdRouteComponent, - remountDeps: opts?.remountDeps.fooId, + remountDeps: opts?.remountDeps.foo2Id, }) function Foo2IdRouteComponent() { @@ -1039,7 +1039,7 @@ describe('router rendering stability', () => { getParentRoute: () => foo2IdRoute, path: '/bar2/$bar2Id', component: Bar2IdRouteComponent, - remountDeps: opts?.remountDeps.barId, + remountDeps: opts?.remountDeps.bar2Id, }) function Bar2IdRouteComponent() { diff --git a/packages/solid-router/src/useParams.tsx b/packages/solid-router/src/useParams.tsx index 8d4543b81f4..70dff667e11 100644 --- a/packages/solid-router/src/useParams.tsx +++ b/packages/solid-router/src/useParams.tsx @@ -73,11 +73,15 @@ export function useParams< select: (match) => (isStrict ? match.id : match), }) as Accessor - const params = isStrict - ? router.getMatch(matchResult())!.params - : matchResult().params + const params = createMemo(() => { + const res = matchResult() - return createMemo(() => - opts.select ? (opts.select(params) as any) : (params ?? {}), - ) + return isStrict ? router.getMatch(res)?.params : res.params + }) + + return createMemo(() => { + const p = params() + + return opts.select ? (opts.select(p) as any) : (p ?? {}) + }) } From 4b8103166a9f3219bac495c4b2a2c45a0076b861 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 15:02:24 +0200 Subject: [PATCH 6/7] few minor nitpick resolutions --- .../src/routes/params-ps/named/$foo/$bar.route.tsx | 8 ++++---- .../src/routes/params-ps/named/$foo/route.tsx | 4 ++-- packages/react-router/src/useParams.tsx | 2 +- packages/react-router/tests/router.test.tsx | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) 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 index 85006de85ef..4387aed56d5 100644 --- 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 @@ -11,8 +11,8 @@ export const Route = createFileRoute('/params-ps/named/$foo/$bar')({ }) function RouteComponent() { - const [renderBarCount, setBarRenderCountCount] = createSignal(0) - const [renderBazCount, setBazRenderCountCount] = createSignal(0) + const [renderBarCount, setBarRenderCount] = createSignal(0) + const [renderBazCount, setBazRenderCount] = createSignal(0) const params = useParams({ strict: false, @@ -20,12 +20,12 @@ function RouteComponent() { createEffect(() => { params().bar - setBarRenderCountCount((prev) => prev + 1) + setBarRenderCount((prev) => prev + 1) }) createEffect(() => { params().baz - setBazRenderCountCount((prev) => prev + 1) + setBazRenderCount((prev) => prev + 1) }) return ( 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 index 92122a0f970..58a78e38223 100644 --- 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 @@ -6,13 +6,13 @@ export const Route = createFileRoute('/params-ps/named/$foo')({ }) function RouteComponent() { - const [renderCount, setRenderCountCount] = createSignal(0) + const [renderCount, setRenderCount] = createSignal(0) const params = Route.useParams() createEffect(() => { params().foo - setRenderCountCount((prev) => prev + 1) + setRenderCount((prev) => prev + 1) }) return ( diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx index f06cd8fa6de..4adca2af0b0 100644 --- a/packages/react-router/src/useParams.tsx +++ b/packages/react-router/src/useParams.tsx @@ -88,7 +88,7 @@ export function useParams< const matchResult = useMatch({ from: opts.from!, - shouldThrow: false, + shouldThrow: opts.shouldThrow, structuralSharing: opts.structuralSharing, strict: opts.strict, select: (match) => (isStrict ? match.id : match), diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index ae40d8d8020..a22dcc22e58 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -950,7 +950,7 @@ describe('router rendering stability', () => { to="/foo2/$foo2Id/bar2/$bar2Id" params={{ foo2Id: '2', bar2Id: '1' }} > - Foo2-2-Bar2_2 + Foo2-2-Bar2_1
From 2be5ebaa2f9892db359ef4bfb57391fb4fb48287 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 6 Sep 2025 16:49:52 +0200 Subject: [PATCH 7/7] make use of _strictParams --- packages/react-router/src/useParams.tsx | 19 +++++--------- .../tests/optional-path-params.test.tsx | 2 +- packages/router-core/src/path.ts | 4 +++ packages/solid-router/src/useParams.tsx | 26 +++++-------------- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx index 4adca2af0b0..7ff3d3934db 100644 --- a/packages/react-router/src/useParams.tsx +++ b/packages/react-router/src/useParams.tsx @@ -1,5 +1,4 @@ import { useMatch } from './useMatch' -import { useRouter } from './useRouter' import type { StructuralSharingOption, ValidateSelected, @@ -82,21 +81,15 @@ export function useParams< UseParamsResult, TThrow > { - const router = useRouter() - - const isStrict = opts.strict !== false - - const matchResult = useMatch({ + return useMatch({ from: opts.from!, shouldThrow: opts.shouldThrow, structuralSharing: opts.structuralSharing, strict: opts.strict, - select: (match) => (isStrict ? match.id : match), - }) as any + select: (match) => { + const params = opts.strict === false ? match.params : match._strictParams - const params = isStrict - ? router.getMatch(matchResult)?.params - : matchResult.params - - return opts.select ? (opts.select(params) as any) : (params ?? {}) + 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/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 70dff667e11..1788f69fe94 100644 --- a/packages/solid-router/src/useParams.tsx +++ b/packages/solid-router/src/useParams.tsx @@ -1,6 +1,4 @@ -import { createMemo } from 'solid-js' import { useMatch } from './useMatch' -import { useRouter } from './useRouter' import type { Accessor } from 'solid-js' import type { AnyRouter, @@ -62,26 +60,14 @@ export function useParams< ): Accessor< ThrowOrOptional, TThrow> > { - const router = useRouter() - - const isStrict = opts.strict !== false - - const matchResult = useMatch({ + return useMatch({ from: opts.from!, shouldThrow: opts.shouldThrow, strict: opts.strict, - select: (match) => (isStrict ? match.id : match), - }) as Accessor - - const params = createMemo(() => { - const res = matchResult() + select: (match) => { + const params = opts.strict === false ? match.params : match._strictParams - return isStrict ? router.getMatch(res)?.params : res.params - }) - - return createMemo(() => { - const p = params() - - return opts.select ? (opts.select(p) as any) : (p ?? {}) - }) + return opts.select ? opts.select(params) : params + }, + }) as Accessor }