diff --git a/.changeset/wide-shoes-invite.md b/.changeset/wide-shoes-invite.md new file mode 100644 index 00000000000..79613137de9 --- /dev/null +++ b/.changeset/wide-shoes-invite.md @@ -0,0 +1,5 @@ +--- +'@tanstack/react-router': patch +--- + +Upgrade `@tanstack/react-store` to `1.0.0-alpha.0` to use React's native `useSyncExternalStore` and remove its external-store shim dependency. diff --git a/packages/react-router/package.json b/packages/react-router/package.json index fca9731bdea..e789defb135 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -93,7 +93,7 @@ }, "dependencies": { "@tanstack/history": "workspace:*", - "@tanstack/react-store": "^0.11.2", + "@tanstack/react-store": "^1.0.0-alpha.0", "@tanstack/router-core": "workspace:*", "isbot": "catalog:" }, diff --git a/packages/react-router/tests/useLocation.test.tsx b/packages/react-router/tests/useLocation.test.tsx index ab3dce78cde..083bd1120fc 100644 --- a/packages/react-router/tests/useLocation.test.tsx +++ b/packages/react-router/tests/useLocation.test.tsx @@ -1,6 +1,13 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { afterEach, describe, expect, test, vi } from 'vitest' -import { act, cleanup, render, screen, waitFor } from '@testing-library/react' +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' import { Outlet, RouterProvider, @@ -9,6 +16,7 @@ import { createRoute, createRouter, useLocation, + useRouterState, } from '../src' afterEach(() => { @@ -17,6 +25,75 @@ afterEach(() => { }) describe('useLocation', () => { + test('updates allocating selectors when their captured props change without navigation', async () => { + function Selection({ prefix }: { prefix: string }) { + const location = useLocation({ + select: (value) => ({ label: `${prefix}${value.pathname}` }), + structuralSharing: false, + }) + const state = useRouterState({ + select: (value) => ({ label: `${prefix}${value.location.pathname}` }), + structuralSharing: false, + }) + + return ( + <> +

{location.label}

+

{state.label}

+ + ) + } + + const rootRoute = createRootRoute({ + component: function RootComponent() { + const [prefix, setPrefix] = useState('first:') + + return ( + <> + + + + + ) + }, + }) + const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + component: () =>

Posts

, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([postsRoute]), + history: createMemoryHistory({ initialEntries: ['/posts'] }), + }) + + render() + + expect( + await screen.findByRole('heading', { name: 'Posts' }), + ).toBeInTheDocument() + await waitFor(() => expect(router.state.status).toBe('idle')) + expect(screen.getByTestId('location-selection')).toHaveTextContent( + 'first:/posts', + ) + expect(screen.getByTestId('state-selection')).toHaveTextContent( + 'first:/posts', + ) + const initialState = router.state + + fireEvent.click(screen.getByRole('button', { name: 'Change selector' })) + + expect(screen.getByTestId('location-selection')).toHaveTextContent( + 'second:/posts', + ) + expect(screen.getByTestId('state-selection')).toHaveTextContent( + 'second:/posts', + ) + expect(router.state).toBe(initialState) + }) + test('keeps a selected pathname reference stable across search and hash updates when structural sharing is enabled', async () => { const effectSpy = vi.fn() const pathnameSelections: Array<{ pathname: string }> = [] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bacfddc7109..c8a10a92c7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13914,8 +13914,8 @@ importers: specifier: workspace:* version: link:../history '@tanstack/react-store': - specifier: ^0.11.2 - version: 0.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.0.0-alpha.0 + version: 1.0.0-alpha.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/router-core': specifier: workspace:* version: link:../router-core @@ -21728,8 +21728,8 @@ packages: peerDependencies: react: ^19.2.3 - '@tanstack/react-store@0.11.2': - resolution: {integrity: sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==} + '@tanstack/react-store@1.0.0-alpha.0': + resolution: {integrity: sha512-9nP706EWgwrXUERJjE7k50ZgQfnALMgma9fvwiSdN1bGdyJutVB9sVe0/BestCHO0ghiJWJshGn8qFp6uOiNKw==} peerDependencies: react: ^19.2.3 react-dom: ^19.2.3 @@ -35749,12 +35749,11 @@ snapshots: '@tanstack/query-core': 5.102.0 react: 19.2.3 - '@tanstack/react-store@0.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-store@1.0.0-alpha.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/store': 0.11.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - use-sync-external-store: 1.6.0(react@19.2.3) '@tanstack/react-virtual@3.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5319b48a691..ed2b17d4300 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,7 @@ cleanupUnusedCatalogs: true minimumReleaseAge: 1440 +minimumReleaseAgeExclude: + - '@tanstack/react-store@1.0.0-alpha.0' linkWorkspacePackages: true preferWorkspacePackages: true blockExoticSubdeps: true