From 97d98428e9ac6532010537cf1c6c555f38078756 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 26 Mar 2026 23:03:29 +0100 Subject: [PATCH] refactor: use local matches in adapter Match components Stop re-looking up matches by id inside the React, Solid, and Vue Match components. Each adapter now reads the promise and pending state from its existing local match source, and the Vue store update expectations are adjusted to reflect the lower update count from that change. --- packages/react-router/src/Match.tsx | 23 +++++++++--------- packages/solid-router/src/Match.tsx | 24 +++++++------------ packages/vue-router/src/Match.tsx | 13 +++++----- .../store-updates-during-navigation.test.tsx | 4 ++-- 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/packages/react-router/src/Match.tsx b/packages/react-router/src/Match.tsx index 04e354d0835..c2c033a0fd4 100644 --- a/packages/react-router/src/Match.tsx +++ b/packages/react-router/src/Match.tsx @@ -287,15 +287,15 @@ export const MatchInner = React.memo(function MatchInnerImpl({ const out = Comp ? : if (match._displayPending) { - throw router.getMatch(match.id)?._nonReactive.displayPendingPromise + throw match._nonReactive.displayPendingPromise } if (match._forcePending) { - throw router.getMatch(match.id)?._nonReactive.minPendingPromise + throw match._nonReactive.minPendingPromise } if (match.status === 'pending') { - throw router.getMatch(match.id)?._nonReactive.loadPromise + throw match._nonReactive.loadPromise } if (match.status === 'notFound') { @@ -317,7 +317,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({ invariant() } - throw router.getMatch(match.id)?._nonReactive.loadPromise + throw match._nonReactive.loadPromise } if (match.status === 'error') { @@ -384,11 +384,11 @@ export const MatchInner = React.memo(function MatchInnerImpl({ }, [key, route.options.component, router.options.defaultComponent]) if (match._displayPending) { - throw router.getMatch(match.id)?._nonReactive.displayPendingPromise + throw match._nonReactive.displayPendingPromise } if (match._forcePending) { - throw router.getMatch(match.id)?._nonReactive.minPendingPromise + throw match._nonReactive.minPendingPromise } // see also hydrate() in packages/router-core/src/ssr/ssr-client.ts @@ -397,23 +397,22 @@ export const MatchInner = React.memo(function MatchInnerImpl({ const pendingMinMs = route.options.pendingMinMs ?? router.options.defaultPendingMinMs if (pendingMinMs) { - const routerMatch = router.getMatch(match.id) - if (routerMatch && !routerMatch._nonReactive.minPendingPromise) { + if (!match._nonReactive.minPendingPromise) { // Create a promise that will resolve after the minPendingMs if (!(isServer ?? router.isServer)) { const minPendingPromise = createControlledPromise() - routerMatch._nonReactive.minPendingPromise = minPendingPromise + match._nonReactive.minPendingPromise = minPendingPromise setTimeout(() => { minPendingPromise.resolve() // We've handled the minPendingPromise, so we can delete it - routerMatch._nonReactive.minPendingPromise = undefined + match._nonReactive.minPendingPromise = undefined }, pendingMinMs) } } } - throw router.getMatch(match.id)?._nonReactive.loadPromise + throw match._nonReactive.loadPromise } if (match.status === 'notFound') { @@ -442,7 +441,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({ // false, // 'Tried to render a redirected route match! This is a weird circumstance, please file an issue!', // ) - throw router.getMatch(match.id)?._nonReactive.loadPromise + throw match._nonReactive.loadPromise } if (match.status === 'error') { diff --git a/packages/solid-router/src/Match.tsx b/packages/solid-router/src/Match.tsx index 9274c86638b..7ed8ca29eef 100644 --- a/packages/solid-router/src/Match.tsx +++ b/packages/solid-router/src/Match.tsx @@ -276,9 +276,7 @@ export const MatchInner = (): any => { {(_) => { const [displayPendingResult] = Solid.createResource( - () => - router.getMatch(currentMatch().id)?._nonReactive - .displayPendingPromise, + () => match()?._nonReactive.displayPendingPromise, ) return <>{displayPendingResult()} @@ -287,9 +285,7 @@ export const MatchInner = (): any => { {(_) => { const [minPendingResult] = Solid.createResource( - () => - router.getMatch(currentMatch().id)?._nonReactive - .minPendingPromise, + () => match()?._nonReactive.minPendingPromise, ) return <>{minPendingResult()} @@ -302,22 +298,22 @@ export const MatchInner = (): any => { router.options.defaultPendingMinMs if (pendingMinMs) { - const routerMatch = router.getMatch(currentMatch().id) + const matchState = match() if ( - routerMatch && - !routerMatch._nonReactive.minPendingPromise + matchState && + !matchState._nonReactive.minPendingPromise ) { // Create a promise that will resolve after the minPendingMs if (!(isServer ?? router.isServer)) { const minPendingPromise = createControlledPromise() - routerMatch._nonReactive.minPendingPromise = + matchState._nonReactive.minPendingPromise = minPendingPromise setTimeout(() => { minPendingPromise.resolve() // We've handled the minPendingPromise, so we can delete it - routerMatch._nonReactive.minPendingPromise = undefined + matchState._nonReactive.minPendingPromise = undefined }, pendingMinMs) } } @@ -325,8 +321,7 @@ export const MatchInner = (): any => { const [loaderResult] = Solid.createResource(async () => { await new Promise((r) => setTimeout(r, 0)) - return router.getMatch(currentMatch().id)?._nonReactive - .loadPromise + return match()?._nonReactive.loadPromise }) const FallbackComponent = @@ -379,8 +374,7 @@ export const MatchInner = (): any => { const [loaderResult] = Solid.createResource(async () => { await new Promise((r) => setTimeout(r, 0)) - return router.getMatch(currentMatch().id)?._nonReactive - .loadPromise + return match()?._nonReactive.loadPromise }) return <>{loaderResult()} diff --git a/packages/vue-router/src/Match.tsx b/packages/vue-router/src/Match.tsx index 380ff00843e..81d90d58c04 100644 --- a/packages/vue-router/src/Match.tsx +++ b/packages/vue-router/src/Match.tsx @@ -378,6 +378,7 @@ export const MatchInner = Vue.defineComponent({ } if (match.value.status === 'redirected') { + const currentMatch = activeMatch.value if (!isRedirect(match.value.error)) { if (process.env.NODE_ENV !== 'production') { throw new Error('Invariant failed: Expected a redirect error') @@ -385,7 +386,7 @@ export const MatchInner = Vue.defineComponent({ invariant() } - throw router.getMatch(match.value.id)?._nonReactive.loadPromise + throw currentMatch?._nonReactive.loadPromise } if (match.value.status === 'error') { @@ -414,25 +415,25 @@ export const MatchInner = Vue.defineComponent({ } if (match.value.status === 'pending') { + const currentMatch = activeMatch.value const pendingMinMs = route.value.options.pendingMinMs ?? router.options.defaultPendingMinMs - const routerMatch = router.getMatch(match.value.id) if ( pendingMinMs && - routerMatch && - !routerMatch._nonReactive.minPendingPromise + currentMatch && + !currentMatch._nonReactive.minPendingPromise ) { // Create a promise that will resolve after the minPendingMs if (!(isServer ?? router.isServer)) { const minPendingPromise = createControlledPromise() - routerMatch._nonReactive.minPendingPromise = minPendingPromise + currentMatch._nonReactive.minPendingPromise = minPendingPromise setTimeout(() => { minPendingPromise.resolve() // We've handled the minPendingPromise, so we can delete it - routerMatch._nonReactive.minPendingPromise = undefined + currentMatch._nonReactive.minPendingPromise = undefined }, pendingMinMs) } } diff --git a/packages/vue-router/tests/store-updates-during-navigation.test.tsx b/packages/vue-router/tests/store-updates-during-navigation.test.tsx index 1c40bf7be7e..40040440cb3 100644 --- a/packages/vue-router/tests/store-updates-during-navigation.test.tsx +++ b/packages/vue-router/tests/store-updates-during-navigation.test.tsx @@ -138,7 +138,7 @@ describe("Store doesn't update *too many* times during navigation", () => { // that needs to be done during a navigation. // Any change that increases this number should be investigated. // Note: Vue has different update counts than React/Solid due to different reactivity - expect(updates).toBe(16) + expect(updates).toBe(14) }) test('redirection in preload', async () => { @@ -174,7 +174,7 @@ describe("Store doesn't update *too many* times during navigation", () => { // that needs to be done during a navigation. // Any change that increases this number should be investigated. // Note: Vue has different update counts than React/Solid due to different reactivity - expect(updates).toBe(12) + expect(updates).toBe(10) }) test('nothing', async () => {