From 09548b7285eca9c9ebb21413e8dd9cd3dfdf2bb7 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Tue, 17 Feb 2026 23:50:53 +0100 Subject: [PATCH 1/3] chore: migrate router to tanstack/store 0.9.1 --- packages/react-router/package.json | 2 +- packages/router-core/package.json | 2 +- packages/router-core/src/router.ts | 30 +++++++++++-------- packages/solid-router/package.json | 2 +- packages/vue-router/package.json | 2 +- pnpm-lock.yaml | 46 +++++++++++++++--------------- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 502c7835f46..ba14dd53e52 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -97,7 +97,7 @@ }, "dependencies": { "@tanstack/history": "workspace:*", - "@tanstack/react-store": "^0.8.0", + "@tanstack/react-store": "^0.9.1", "@tanstack/router-core": "workspace:*", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", diff --git a/packages/router-core/package.json b/packages/router-core/package.json index da3a5de4441..d5d5d016e0c 100644 --- a/packages/router-core/package.json +++ b/packages/router-core/package.json @@ -160,7 +160,7 @@ }, "dependencies": { "@tanstack/history": "workspace:*", - "@tanstack/store": "^0.8.0", + "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index e59c87bb409..a3b07e39256 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -1117,15 +1117,21 @@ export class RouterCore< getInitialRouterState(this.latestLocation), ) as unknown as Store } else { - this.__store = new Store(getInitialRouterState(this.latestLocation), { - onUpdate: () => { - this.__store.state = { - ...this.state, - cachedMatches: this.state.cachedMatches.filter( - (d) => !['redirected'].includes(d.status), - ), - } - }, + this.__store = new Store(getInitialRouterState(this.latestLocation)) + + this.__store.subscribe((state) => { + const cachedMatches = state.cachedMatches.filter( + (d) => d.status !== 'redirected', + ) + + if (cachedMatches.length === state.cachedMatches.length) { + return + } + + this.__store.setState((prev) => ({ + ...prev, + cachedMatches, + })) }) setupScrollRestoration(this) @@ -1169,10 +1175,10 @@ export class RouterCore< } if (needsLocationUpdate && this.__store) { - this.__store.state = { - ...this.state, + this.__store.setState((s) => ({ + ...s, location: this.latestLocation, - } + })) } if ( diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json index 4620a55842c..bcc5c317b4f 100644 --- a/packages/solid-router/package.json +++ b/packages/solid-router/package.json @@ -106,7 +106,7 @@ "@solidjs/meta": "^0.29.4", "@tanstack/history": "workspace:*", "@tanstack/router-core": "workspace:*", - "@tanstack/solid-store": "^0.8.0", + "@tanstack/solid-store": "^0.9.1", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index 5f0f7916aac..37e2d6a34e3 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -72,7 +72,7 @@ "dependencies": { "@tanstack/history": "workspace:*", "@tanstack/router-core": "workspace:*", - "@tanstack/vue-store": "^0.8.0", + "@tanstack/vue-store": "^0.9.1", "@vue/runtime-dom": "^3.5.25", "isbot": "^5.1.22", "jsesc": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed222422805..5af195838c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11552,8 +11552,8 @@ importers: specifier: workspace:* version: link:../history '@tanstack/react-store': - specifier: ^0.8.0 - version: 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/router-core': specifier: workspace:* version: link:../router-core @@ -11762,8 +11762,8 @@ importers: specifier: workspace:* version: link:../history '@tanstack/store': - specifier: ^0.8.0 - version: 0.8.0 + specifier: ^0.9.1 + version: 0.9.1 cookie-es: specifier: ^2.0.0 version: 2.0.0 @@ -12014,8 +12014,8 @@ importers: specifier: workspace:* version: link:../router-core '@tanstack/solid-store': - specifier: ^0.8.0 - version: 0.8.0(solid-js@1.9.10) + specifier: ^0.9.1 + version: 0.9.1(solid-js@1.9.10) isbot: specifier: ^5.1.22 version: 5.1.28 @@ -12371,8 +12371,8 @@ importers: specifier: workspace:* version: link:../router-core '@tanstack/vue-store': - specifier: ^0.8.0 - version: 0.8.0(vue@3.5.25(typescript@5.9.3)) + specifier: ^0.9.1 + version: 0.9.1(vue@3.5.25(typescript@5.9.3)) '@vue/runtime-dom': specifier: ^3.5.25 version: 3.5.25 @@ -17490,8 +17490,8 @@ packages: peerDependencies: react: ^19.2.3 - '@tanstack/react-store@0.8.0': - resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + '@tanstack/react-store@0.9.1': + resolution: {integrity: sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==} peerDependencies: react: ^19.2.3 react-dom: ^19.2.3 @@ -17519,8 +17519,8 @@ packages: peerDependencies: solid-js: 1.9.10 - '@tanstack/solid-store@0.8.0': - resolution: {integrity: sha512-JwqTedbxyOGw7mfmdGkB0RGgefRCw/tNauc8tlMcaS1mV5wTFT8c1KIB3LgttuHaanMJEBeqQJ7bc/R0WTP1fA==} + '@tanstack/solid-store@0.9.1': + resolution: {integrity: sha512-gx7ToM+Yrkui36NIj0HjAufzv1Dg8usjtVFy5H3Ll52Xjuz+eliIJL+ihAr4LRuWh3nDPBR+nCLW0ShFrbE5yw==} peerDependencies: solid-js: 1.9.10 @@ -17529,8 +17529,8 @@ packages: peerDependencies: solid-js: 1.9.10 - '@tanstack/store@0.8.0': - resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + '@tanstack/store@0.9.1': + resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==} '@tanstack/typedoc-config@0.3.0': resolution: {integrity: sha512-g7sfxscIq0wYUGtOLegnTbiMTsNiAz6r28CDgdZqIIjI1naWZoIlABpWH2qdI3IIJUDWvhOaVwAo6sfqzm6GsQ==} @@ -17579,8 +17579,8 @@ packages: '@vue/composition-api': optional: true - '@tanstack/vue-store@0.8.0': - resolution: {integrity: sha512-YLsinYboBLIjNkxDpAn1ydaMS35dKq3M3a788JRCJi4/stWcN7Swp0pxxJ+p0IwKSY4tBXx7vMz22OYWQ1QsUQ==} + '@tanstack/vue-store@0.9.1': + resolution: {integrity: sha512-mXXZzPWom656MExX2gG1fqopJhToDbqGEl98WtJ5/hyouQHtQXiAgtsPNLzUcVcwU9okM/OCWv7QAgXf6C5ziQ==} peerDependencies: '@vue/composition-api': ^1.2.1 vue: ^2.5.0 || ^3.0.0 @@ -29991,9 +29991,9 @@ snapshots: '@tanstack/query-core': 5.90.19 react: 19.2.3 - '@tanstack/react-store@0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-store@0.9.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/store': 0.8.0 + '@tanstack/store': 0.9.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) @@ -30024,9 +30024,9 @@ snapshots: '@tanstack/query-core': 5.90.19 solid-js: 1.9.10 - '@tanstack/solid-store@0.8.0(solid-js@1.9.10)': + '@tanstack/solid-store@0.9.1(solid-js@1.9.10)': dependencies: - '@tanstack/store': 0.8.0 + '@tanstack/store': 0.9.1 solid-js: 1.9.10 '@tanstack/solid-virtual@3.13.12(solid-js@1.9.10)': @@ -30034,7 +30034,7 @@ snapshots: '@tanstack/virtual-core': 3.13.12 solid-js: 1.9.10 - '@tanstack/store@0.8.0': {} + '@tanstack/store@0.9.1': {} '@tanstack/typedoc-config@0.3.0(typescript@5.9.3)': dependencies: @@ -30119,9 +30119,9 @@ snapshots: vue: 3.5.25(typescript@5.9.2) vue-demi: 0.14.10(vue@3.5.25(typescript@5.9.2)) - '@tanstack/vue-store@0.8.0(vue@3.5.25(typescript@5.9.3))': + '@tanstack/vue-store@0.9.1(vue@3.5.25(typescript@5.9.3))': dependencies: - '@tanstack/store': 0.8.0 + '@tanstack/store': 0.9.1 vue: 3.5.25(typescript@5.9.3) vue-demi: 0.14.10(vue@3.5.25(typescript@5.9.3)) From 88625dcd797aec8e4a9b9cbf12d9c6bfb586b209 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Wed, 18 Feb 2026 00:25:29 +0100 Subject: [PATCH 2/3] refactor(router-core): target redirected cache filtering to cached update path --- packages/router-core/src/router.ts | 45 ++++++++++++------------ packages/router-core/tests/load.test.ts | 46 +++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index a3b07e39256..33e95b4684e 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -865,6 +865,13 @@ export function getLocationChangeInfo(routerState: { return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged } } +function filterRedirectedCachedMatches( + matches: Array, +): Array { + const filtered = matches.filter((d) => d.status !== 'redirected') + return filtered.length === matches.length ? matches : filtered +} + export type CreateRouterFn = < TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = 'never', @@ -1119,21 +1126,6 @@ export class RouterCore< } else { this.__store = new Store(getInitialRouterState(this.latestLocation)) - this.__store.subscribe((state) => { - const cachedMatches = state.cachedMatches.filter( - (d) => d.status !== 'redirected', - ) - - if (cachedMatches.length === state.cachedMatches.length) { - return - } - - this.__store.setState((prev) => ({ - ...prev, - cachedMatches, - })) - }) - setupScrollRestoration(this) } } @@ -2445,7 +2437,7 @@ export class RouterCore< ...s.cachedMatches, ...exitingMatches.filter( (d) => - d.status !== 'error' && d.status !== 'notFound', + d.status !== 'error' && d.status !== 'notFound' && d.status !== 'redirected', ), ], } @@ -2600,12 +2592,21 @@ export class RouterCore< : '' if (matchesKey) { - this.__store.setState((s) => ({ - ...s, - [matchesKey]: s[matchesKey]?.map((d) => - d.id === id ? updater(d) : d, - ), - })) + if (matchesKey === 'cachedMatches') { + this.__store.setState((s) => ({ + ...s, + cachedMatches: filterRedirectedCachedMatches( + s.cachedMatches.map((d) => (d.id === id ? updater(d) : d)), + ), + })) + } else { + this.__store.setState((s) => ({ + ...s, + [matchesKey]: s[matchesKey]?.map((d) => + d.id === id ? updater(d) : d, + ), + })) + } } }) } diff --git a/packages/router-core/tests/load.test.ts b/packages/router-core/tests/load.test.ts index b312cfe71c8..1c2e17c5662 100644 --- a/packages/router-core/tests/load.test.ts +++ b/packages/router-core/tests/load.test.ts @@ -167,10 +167,16 @@ describe('beforeLoad skip or exec', () => { beforeLoad, }) await router.preloadRoute({ to: '/foo' }) + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) await sleep(10) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -184,9 +190,15 @@ describe('beforeLoad skip or exec', () => { }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -362,10 +374,16 @@ describe('loader skip or exec', () => { loader, }) await router.preloadRoute({ to: '/foo' }) + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) await sleep(10) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) expect(loader).toHaveBeenCalledTimes(2) }) @@ -379,12 +397,40 @@ describe('loader skip or exec', () => { }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/bar') + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) expect(loader).toHaveBeenCalledTimes(1) }) + test('updateMatch removes redirected matches from cachedMatches', async () => { + const loader = vi.fn() + const router = setup({ loader }) + + await router.preloadRoute({ to: '/foo' }) + expect(router.state.cachedMatches).toEqual( + expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), + ) + + router.updateMatch('/foo/foo', (prev) => ({ + ...prev, + status: 'redirected', + })) + + expect(router.state.cachedMatches.some((d) => d.id === '/foo/foo')).toBe( + false, + ) + expect( + router.state.cachedMatches.some((d) => d.status === 'redirected'), + ).toBe(false) + }) + test('exec if rejected preload (error)', async () => { const loader = vi.fn(async ({ preload }) => { if (preload) throw new Error('error') From 7cd4c1fca2339986382f3e497e054638d7bd8ebb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:27:17 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- packages/router-core/src/router.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 33e95b4684e..510f85d0207 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -2437,7 +2437,9 @@ export class RouterCore< ...s.cachedMatches, ...exitingMatches.filter( (d) => - d.status !== 'error' && d.status !== 'notFound' && d.status !== 'redirected', + d.status !== 'error' && + d.status !== 'notFound' && + d.status !== 'redirected', ), ], }