diff --git a/packages/react-router/package.json b/packages/react-router/package.json index b8eca97196b..7a59689048b 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 4bd9fc9a65b..11293a27e61 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 5ff3a6e93a3..23e9f99d0c6 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -871,6 +871,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', @@ -1123,16 +1130,7 @@ 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)) setupScrollRestoration(this) } @@ -1175,10 +1173,10 @@ export class RouterCore< } if (needsLocationUpdate && this.__store) { - this.__store.state = { - ...this.state, + this.__store.setState((s) => ({ + ...s, location: this.latestLocation, - } + })) } if ( @@ -2445,7 +2443,9 @@ 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 +2600,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') diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json index 518d1f5f294..ca22499e448 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 30b15e2bf16..82c33737059 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 24046b73ef3..b9d199d23d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11592,8 +11592,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 @@ -11802,8 +11802,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 @@ -12054,8 +12054,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 @@ -12420,8 +12420,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 @@ -17539,8 +17539,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 @@ -17568,8 +17568,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 @@ -17578,8 +17578,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==} @@ -17628,8 +17628,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 @@ -30043,9 +30043,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) @@ -30076,9 +30076,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)': @@ -30086,7 +30086,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: @@ -30171,9 +30171,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))