diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx index 92c215a0e9a..80578b6f4a3 100644 --- a/packages/react-router/tests/link.test.tsx +++ b/packages/react-router/tests/link.test.tsx @@ -4024,8 +4024,8 @@ describe('Link', () => { ), }) - const parseParams = vi.fn() - const stringifyParams = vi.fn() + let parseParams: any + let stringifyParams: any const PostComponent = () => { const params = useParams({ strict: false }) @@ -4036,14 +4036,14 @@ describe('Link', () => { getParentRoute: () => rootRoute, path: '$postId', parseParams: (params) => { - parseParams(params) + parseParams = structuredClone(params) // clone object, because source will get mutated return { status: 'parsed', postId: params.postId, } }, stringifyParams: (params) => { - stringifyParams(params) + stringifyParams = structuredClone(params) // clone object, because source will get mutated return { status: 'stringified', postId: params.postId, @@ -4061,7 +4061,7 @@ describe('Link', () => { name: 'Go to post', }) - expect(stringifyParams).toHaveBeenCalledWith({ postId: 2 }) + expect(stringifyParams).toEqual({ postId: 2 }) expect(postLink).toHaveAttribute('href', '/2') @@ -4070,7 +4070,7 @@ describe('Link', () => { const posts2Text = await screen.findByText('Post: 2') expect(posts2Text).toBeInTheDocument() - expect(parseParams).toHaveBeenCalledWith({ status: 'parsed', postId: '2' }) + expect(parseParams).toEqual({ postId: '2' }) }) test('when navigating to /$postId with params.parse and params.stringify', async () => { @@ -4086,8 +4086,8 @@ describe('Link', () => { ), }) - const parseParams = vi.fn() - const stringifyParams = vi.fn() + let parseParams: any + let stringifyParams: any const PostComponent = () => { const params = useParams({ strict: false }) @@ -4099,14 +4099,14 @@ describe('Link', () => { path: '$postId', params: { parse: (params) => { - parseParams(params) + parseParams = structuredClone(params) // clone object, because source will get mutated return { status: 'parsed', postId: params.postId, } }, stringify: (params) => { - stringifyParams(params) + stringifyParams = structuredClone(params) // clone object, because source will get mutated return { status: 'stringified', postId: params.postId, @@ -4125,7 +4125,7 @@ describe('Link', () => { name: 'Go to post', }) - expect(stringifyParams).toHaveBeenCalledWith({ postId: 2 }) + expect(stringifyParams).toEqual({ postId: 2 }) expect(postLink).toHaveAttribute('href', '/2') @@ -4134,7 +4134,7 @@ describe('Link', () => { const posts2Text = await screen.findByText('Post: 2') expect(posts2Text).toBeInTheDocument() - expect(parseParams).toHaveBeenCalledWith({ status: 'parsed', postId: '2' }) + expect(parseParams).toEqual({ postId: '2' }) }) test('when navigating to /$postId with params.parse and params.stringify handles falsey inputs', async () => { diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index c4409961b83..64b47c8b674 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -1472,20 +1472,20 @@ export class RouterCore< : this.resolvePathWithBase(fromPath, '.') // Resolve the next params - let nextParams = + const nextParams = dest.params === false || dest.params === null ? {} : (dest.params ?? true) === true ? fromParams - : { - ...fromParams, - ...functionalUpdate(dest.params as any, fromParams), - } + : Object.assign( + fromParams, + functionalUpdate(dest.params as any, fromParams), + ) // Interpolate the path first to get the actual resolved path, then match against that const interpolatedNextTo = interpolatePath({ path: nextTo, - params: nextParams ?? {}, + params: nextParams, parseCache: this.parsePathnameCache, }).interpolatedPath @@ -1495,23 +1495,20 @@ export class RouterCore< // If there are any params, we need to stringify them if (Object.keys(nextParams).length > 0) { - destRoutes - .map((route) => { - return ( - route.options.params?.stringify ?? route.options.stringifyParams - ) - }) - .filter(Boolean) - .forEach((fn) => { - nextParams = { ...nextParams!, ...fn!(nextParams) } - }) + for (const route of destRoutes) { + const fn = + route.options.params?.stringify ?? route.options.stringifyParams + if (fn) { + Object.assign(nextParams, fn(nextParams)) + } + } } const nextPathname = interpolatePath({ // Use the original template path for interpolation // This preserves the original parameter syntax including optional parameters path: nextTo, - params: nextParams ?? {}, + params: nextParams, leaveWildcards: false, leaveParams: opts.leaveParams, decodeCharMap: this.pathParamsDecodeCharMap, @@ -1521,20 +1518,20 @@ export class RouterCore< // Resolve the next search let nextSearch = fromSearch if (opts._includeValidateSearch && this.options.search?.strict) { - let validatedSearch = {} + const validatedSearch = {} destRoutes.forEach((route) => { - try { - if (route.options.validateSearch) { - validatedSearch = { - ...validatedSearch, - ...(validateSearch(route.options.validateSearch, { + if (route.options.validateSearch) { + try { + Object.assign( + validatedSearch, + validateSearch(route.options.validateSearch, { ...validatedSearch, ...nextSearch, - }) ?? {}), - } + }), + ) + } catch { + // ignore errors here because they are already handled in matchRoutes } - } catch { - // ignore errors here because they are already handled in matchRoutes } }) nextSearch = validatedSearch diff --git a/packages/solid-router/tests/link.test.tsx b/packages/solid-router/tests/link.test.tsx index f1243d9f7fb..9eba08fd094 100644 --- a/packages/solid-router/tests/link.test.tsx +++ b/packages/solid-router/tests/link.test.tsx @@ -3592,8 +3592,8 @@ describe('Link', () => { ), }) - const parseParams = vi.fn() - const stringifyParams = vi.fn() + let parseParams: any + let stringifyParams: any const PostComponent = () => { const params = useParams({ strict: false }) @@ -3604,14 +3604,14 @@ describe('Link', () => { getParentRoute: () => rootRoute, path: '$postId', parseParams: (params) => { - parseParams(params) + parseParams = structuredClone(params) // clone object, because source will get mutated return { status: 'parsed', postId: params.postId, } }, stringifyParams: (params) => { - stringifyParams(params) + stringifyParams = structuredClone(params) // clone object, because source will get mutated return { status: 'stringified', postId: params.postId, @@ -3629,7 +3629,7 @@ describe('Link', () => { name: 'Go to post', }) - expect(stringifyParams).toHaveBeenCalledWith({ postId: 2 }) + expect(stringifyParams).toEqual({ postId: 2 }) expect(postLink).toHaveAttribute('href', '/2') @@ -3638,7 +3638,7 @@ describe('Link', () => { const posts2Text = await screen.findByText('Post: 2') expect(posts2Text).toBeInTheDocument() - expect(parseParams).toHaveBeenCalledWith({ status: 'parsed', postId: '2' }) + expect(parseParams).toEqual({ postId: '2' }) }) test('when navigating to /$postId with params.parse and params.stringify', async () => { @@ -3654,8 +3654,8 @@ describe('Link', () => { ), }) - const parseParams = vi.fn() - const stringifyParams = vi.fn() + let parseParams: any + let stringifyParams: any const PostComponent = () => { const params = useParams({ strict: false }) @@ -3667,14 +3667,14 @@ describe('Link', () => { path: '$postId', params: { parse: (params) => { - parseParams(params) + parseParams = structuredClone(params) // clone object, because source will get mutated return { status: 'parsed', postId: params.postId, } }, stringify: (params) => { - stringifyParams(params) + stringifyParams = structuredClone(params) // clone object, because source will get mutated return { status: 'stringified', postId: params.postId, @@ -3693,7 +3693,7 @@ describe('Link', () => { name: 'Go to post', }) - expect(stringifyParams).toHaveBeenCalledWith({ postId: 2 }) + expect(stringifyParams).toEqual({ postId: 2 }) expect(postLink).toHaveAttribute('href', '/2') @@ -3702,7 +3702,7 @@ describe('Link', () => { const posts2Text = await screen.findByText('Post: 2') expect(posts2Text).toBeInTheDocument() - expect(parseParams).toHaveBeenCalledWith({ status: 'parsed', postId: '2' }) + expect(parseParams).toEqual({ postId: '2' }) }) test('when navigating to /$postId with params.parse and params.stringify handles falsey inputs', async () => {