Skip to content

Commit 655496f

Browse files
authored
refactor(router-core): skip full matchRoutesInternal for Link's buildLocation, use simple getMatchedRoutes (#6445)
1 parent 7699e22 commit 655496f

File tree

4 files changed

+56
-40
lines changed

4 files changed

+56
-40
lines changed

packages/react-router/tests/useParams.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ test('useParams must return parsed result if applicable.', async () => {
189189

190190
expect(window.location.pathname).toBe('/posts/category_first')
191191
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
192-
expect(mockedfn).toHaveBeenCalledTimes(1)
192+
expect(mockedfn).not.toHaveBeenCalled()
193193

194194
mockedfn.mockClear()
195195
await act(() => fireEvent.click(firstPostLink))
@@ -213,7 +213,7 @@ test('useParams must return parsed result if applicable.', async () => {
213213
expect(renderedPost.category).toBe('one')
214214
expect(paramCategoryValue.textContent).toBe('one')
215215
expect(paramPostIdValue.textContent).toBe('1')
216-
expect(mockedfn).toHaveBeenCalledTimes(2)
216+
expect(mockedfn).toHaveBeenCalledTimes(1)
217217
expect(allCategoryLink).toBeInTheDocument()
218218

219219
mockedfn.mockClear()
@@ -224,7 +224,7 @@ test('useParams must return parsed result if applicable.', async () => {
224224
expect(window.location.pathname).toBe('/posts/category_all')
225225
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
226226
expect(secondPostLink).toBeInTheDocument()
227-
expect(mockedfn).toHaveBeenCalledTimes(2)
227+
expect(mockedfn).not.toHaveBeenCalled()
228228

229229
mockedfn.mockClear()
230230
await act(() => fireEvent.click(secondPostLink))
@@ -246,5 +246,5 @@ test('useParams must return parsed result if applicable.', async () => {
246246
expect(renderedPost.category).toBe('two')
247247
expect(paramCategoryValue.textContent).toBe('all')
248248
expect(paramPostIdValue.textContent).toBe('2')
249-
expect(mockedfn).toHaveBeenCalledTimes(2)
249+
expect(mockedfn).toHaveBeenCalledTimes(1)
250250
})

packages/router-core/src/router.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,22 +1330,9 @@ export class RouterCore<
13301330
}
13311331
}
13321332

1333-
globalNotFoundRouteId = (() => {
1334-
if (!isGlobalNotFound) {
1335-
return undefined
1336-
}
1337-
1338-
if (this.options.notFoundMode !== 'root') {
1339-
for (let i = matchedRoutes.length - 1; i >= 0; i--) {
1340-
const route = matchedRoutes[i]!
1341-
if (route.children) {
1342-
return route.id
1343-
}
1344-
}
1345-
}
1346-
1347-
return rootRouteId
1348-
})()
1333+
globalNotFoundRouteId = isGlobalNotFound
1334+
? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
1335+
: undefined
13491336
}
13501337

13511338
const matches: Array<AnyRouteMatch> = []
@@ -1775,16 +1762,30 @@ export class RouterCore<
17751762
params: nextParams,
17761763
}).interpolatedPath
17771764

1778-
const { matches: destMatches, rawParams } = this.matchRoutesInternal(
1779-
{ pathname: interpolatedNextTo } as ParsedLocation,
1780-
{ _buildLocation: true },
1781-
)
1782-
const destRoutes = destMatches.map(
1783-
(d) => this.looseRoutesById[d.routeId]!,
1784-
)
1785-
1786-
// Check if any match indicates global not found
1787-
const globalNotFoundMatch = destMatches.find((m) => m.globalNotFound)
1765+
// Use lightweight getMatchedRoutes instead of matchRoutesInternal
1766+
// This avoids creating full match objects (AbortController, ControlledPromise, etc.)
1767+
// which are expensive and not needed for buildLocation
1768+
const destMatchResult = this.getMatchedRoutes(interpolatedNextTo)
1769+
let destRoutes = destMatchResult.matchedRoutes
1770+
const rawParams = destMatchResult.routeParams
1771+
1772+
// Compute globalNotFoundRouteId using the same logic as matchRoutesInternal
1773+
const isGlobalNotFound = destMatchResult.foundRoute
1774+
? destMatchResult.foundRoute.path !== '/' &&
1775+
destMatchResult.routeParams['**']
1776+
: trimPathRight(interpolatedNextTo)
1777+
1778+
let globalNotFoundRouteId: string | undefined
1779+
if (isGlobalNotFound) {
1780+
if (this.options.notFoundRoute) {
1781+
destRoutes = [...destRoutes, this.options.notFoundRoute]
1782+
} else {
1783+
globalNotFoundRouteId = findGlobalNotFoundRouteId(
1784+
this.options.notFoundMode,
1785+
destRoutes,
1786+
)
1787+
}
1788+
}
17881789

17891790
// If there are any params, we need to stringify them
17901791
if (Object.keys(nextParams).length > 0) {
@@ -1877,7 +1878,7 @@ export class RouterCore<
18771878
routes: destRoutes,
18781879
params: snapshotParams,
18791880
searchStr,
1880-
globalNotFoundRouteId: globalNotFoundMatch?.routeId,
1881+
globalNotFoundRouteId,
18811882
})
18821883

18831884
// Create the full path of the location
@@ -3045,7 +3046,7 @@ function applySearchMiddleware({
30453046
}: {
30463047
search: any
30473048
dest: BuildNextOptions
3048-
destRoutes: Array<AnyRoute>
3049+
destRoutes: ReadonlyArray<AnyRoute>
30493050
_includeValidateSearch: boolean | undefined
30503051
}) {
30513052
const allMiddlewares =
@@ -3151,3 +3152,18 @@ function applySearchMiddleware({
31513152
// Start applying middlewares
31523153
return applyNext(0, search)
31533154
}
3155+
3156+
function findGlobalNotFoundRouteId(
3157+
notFoundMode: 'root' | 'fuzzy' | undefined,
3158+
routes: ReadonlyArray<AnyRoute>,
3159+
) {
3160+
if (notFoundMode !== 'root') {
3161+
for (let i = routes.length - 1; i >= 0; i--) {
3162+
const route = routes[i]!
3163+
if (route.children) {
3164+
return route.id
3165+
}
3166+
}
3167+
}
3168+
return rootRouteId
3169+
}

packages/solid-router/tests/useParams.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ test('useParams must return parsed result if applicable.', async () => {
188188

189189
expect(window.location.pathname).toBe('/posts/category_first')
190190
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
191-
expect(mockedfn).toHaveBeenCalledTimes(1)
191+
expect(mockedfn).not.toHaveBeenCalled()
192192

193193
mockedfn.mockClear()
194194
await waitFor(() => fireEvent.click(firstPostLink))
@@ -211,7 +211,7 @@ test('useParams must return parsed result if applicable.', async () => {
211211
expect(renderedPost.category).toBe('one')
212212
expect(paramCategoryValue.textContent).toBe('one')
213213
expect(paramPostIdValue.textContent).toBe('1')
214-
expect(mockedfn).toHaveBeenCalledTimes(2)
214+
expect(mockedfn).toHaveBeenCalledTimes(1)
215215
expect(allCategoryLink).toBeInTheDocument()
216216

217217
mockedfn.mockClear()
@@ -222,7 +222,7 @@ test('useParams must return parsed result if applicable.', async () => {
222222
expect(window.location.pathname).toBe('/posts/category_all')
223223
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
224224
expect(secondPostLink).toBeInTheDocument()
225-
expect(mockedfn).toHaveBeenCalledTimes(2)
225+
expect(mockedfn).not.toHaveBeenCalled()
226226

227227
mockedfn.mockClear()
228228
await waitFor(() => fireEvent.click(secondPostLink))
@@ -244,5 +244,5 @@ test('useParams must return parsed result if applicable.', async () => {
244244
expect(renderedPost.category).toBe('two')
245245
expect(paramCategoryValue.textContent).toBe('all')
246246
expect(paramPostIdValue.textContent).toBe('2')
247-
expect(mockedfn).toHaveBeenCalledTimes(2)
247+
expect(mockedfn).toHaveBeenCalledTimes(1)
248248
})

packages/vue-router/tests/useParams.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ test('useParams must return parsed result if applicable.', async () => {
193193

194194
expect(window.location.pathname).toBe('/posts/category_first')
195195
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
196-
expect(mockedfn).toHaveBeenCalledTimes(1)
196+
expect(mockedfn).not.toHaveBeenCalled()
197197

198198
mockedfn.mockClear()
199199
await waitFor(() => fireEvent.click(firstPostLink))
@@ -216,7 +216,7 @@ test('useParams must return parsed result if applicable.', async () => {
216216
expect(renderedPost.category).toBe('one')
217217
expect(paramCategoryValue.textContent).toBe('one')
218218
expect(paramPostIdValue.textContent).toBe('1')
219-
expect(mockedfn).toHaveBeenCalledTimes(2)
219+
expect(mockedfn).toHaveBeenCalledTimes(1)
220220
expect(allCategoryLink).toBeInTheDocument()
221221

222222
mockedfn.mockClear()
@@ -227,7 +227,7 @@ test('useParams must return parsed result if applicable.', async () => {
227227
expect(window.location.pathname).toBe('/posts/category_all')
228228
expect(await screen.findByTestId('post-category-heading')).toBeInTheDocument()
229229
expect(secondPostLink).toBeInTheDocument()
230-
expect(mockedfn).toHaveBeenCalledTimes(2)
230+
expect(mockedfn).not.toHaveBeenCalled()
231231

232232
mockedfn.mockClear()
233233
await waitFor(() => fireEvent.click(secondPostLink))
@@ -249,5 +249,5 @@ test('useParams must return parsed result if applicable.', async () => {
249249
expect(renderedPost.category).toBe('two')
250250
expect(paramCategoryValue.textContent).toBe('all')
251251
expect(paramPostIdValue.textContent).toBe('2')
252-
expect(mockedfn).toHaveBeenCalledTimes(2)
252+
expect(mockedfn).toHaveBeenCalledTimes(1)
253253
})

0 commit comments

Comments
 (0)