diff --git a/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts b/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts new file mode 100644 index 000000000..c14a6afc5 --- /dev/null +++ b/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts @@ -0,0 +1,12 @@ +it('does not append a trailing slash', () => { + cy.visit('/trailing-slash') + cy.location('pathname').should('eq', '/trailing-slash') + cy.contains('Set declared').click() + cy.location('pathname').should('eq', '/trailing-slash') + cy.contains('Clear').click() + cy.location('pathname').should('eq', '/trailing-slash') + cy.contains('Set undeclared').click() + cy.location('pathname').should('eq', '/trailing-slash') + cy.contains('Clear').click() + cy.location('pathname').should('eq', '/trailing-slash') +}) diff --git a/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx b/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx new file mode 100644 index 000000000..1bb989ad0 --- /dev/null +++ b/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx @@ -0,0 +1,50 @@ +import { createFileRoute } from '@tanstack/react-router' +import { createStandardSchemaV1, parseAsString, useQueryStates } from 'nuqs' + +const declared = { + declared: parseAsString +} +const undeclared = { + undeclared: parseAsString +} + +const searchParams = { + ...declared, + ...undeclared +} + +export const Route = createFileRoute('/trailing-slash')({ + validateSearch: createStandardSchemaV1(declared, { + partialOutput: true + }), + component: TrailingSlashTest +}) + +function TrailingSlashTest() { + const [{ declared, undeclared }, setSearchParams] = + useQueryStates(searchParams) + return ( +
+ + + +
{JSON.stringify({ declared, undeclared }, null, 2)}
+
+ ) +} diff --git a/packages/nuqs/src/adapters/tanstack-router.ts b/packages/nuqs/src/adapters/tanstack-router.ts index 3ec90639b..91475d436 100644 --- a/packages/nuqs/src/adapters/tanstack-router.ts +++ b/packages/nuqs/src/adapters/tanstack-router.ts @@ -1,10 +1,11 @@ -import { useLocation, useMatches, useNavigate } from '@tanstack/react-router' +import { useLocation, useNavigate } from '@tanstack/react-router' import { startTransition, useCallback, useMemo } from 'react' import { renderQueryString } from '../lib/url-encoding' import { createAdapterProvider, type AdapterProvider } from './lib/context' import type { AdapterInterface, UpdateUrlFunction } from './lib/defs' function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface { + const pathname = useLocation({ select: state => state.pathname }) const search = useLocation({ select: state => Object.fromEntries( @@ -12,12 +13,6 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface { ) }) const navigate = useNavigate() - const from = useMatches({ - select: matches => - matches.length > 0 - ? (matches[matches.length - 1]?.fullPath as string) - : undefined - }) const searchParams = useMemo( () => // search is a Record>, @@ -56,20 +51,18 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface { // TBC if it causes issues with consuming those search params // in other parts of the app. // - // When we clear the search, passing an empty string causes - // a type error and possible basepath issues, so we switch it to '.' instead. - // See https://github.com/47ng/nuqs/pull/953#issuecomment-3003583471 - to: renderQueryString(search) || '.', - // `from` will be handled by tanstack router match resolver, code snippet: - // https://github.com/TanStack/router/blob/5d940e2d8bdb12e213eede0abe8012855433ec4b/packages/react-router/src/link.tsx#L108-L112 - ...(from ? { from } : {}), + // Note: we need to specify pathname + search here to avoid TSR appending + // a trailing slash to the pathname, see https://github.com/47ng/nuqs/issues/1215 + from: '/', + to: pathname + renderQueryString(search), replace: options.history === 'replace', resetScroll: options.scroll, - hash: prevHash => prevHash ?? '' + hash: prevHash => prevHash ?? '', + state: state => state }) }) }, - [navigate, from] + [navigate, pathname] ) return {