From f569671a059f3245b5daafef420d16452e0a8ca6 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 10 Oct 2025 08:35:57 +0200 Subject: [PATCH 1/3] tests: add ValidateLinkOptions in tanstack-router-react-example-large-file-based --- .../large-file-based/src/routeTree.gen.ts | 21 +++++++++ .../src/routes/validateLinkOptions.tsx | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 examples/react/large-file-based/src/routes/validateLinkOptions.tsx diff --git a/examples/react/large-file-based/src/routeTree.gen.ts b/examples/react/large-file-based/src/routeTree.gen.ts index 93a5be975c7..411da44b300 100644 --- a/examples/react/large-file-based/src/routeTree.gen.ts +++ b/examples/react/large-file-based/src/routeTree.gen.ts @@ -9,6 +9,7 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as ValidateLinkOptionsRouteImport } from './routes/validateLinkOptions' import { Route as RelativeRouteImport } from './routes/relative' import { Route as LinkPropsRouteImport } from './routes/linkProps' import { Route as AbsoluteRouteImport } from './routes/absolute' @@ -18,6 +19,11 @@ import { Route as IndexRouteImport } from './routes/index' import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' +const ValidateLinkOptionsRoute = ValidateLinkOptionsRouteImport.update({ + id: '/validateLinkOptions', + path: '/validateLinkOptions', + getParentRoute: () => rootRouteImport, +} as any) const RelativeRoute = RelativeRouteImport.update({ id: '/relative', path: '/relative', @@ -66,6 +72,7 @@ export interface FileRoutesByFullPath { '/absolute': typeof AbsoluteRoute '/linkProps': typeof LinkPropsRoute '/relative': typeof RelativeRoute + '/validateLinkOptions': typeof ValidateLinkOptionsRoute '/params/$paramsPlaceholder': typeof ParamsParamsPlaceholderRoute '/search/searchPlaceholder': typeof SearchSearchPlaceholderRoute } @@ -76,6 +83,7 @@ export interface FileRoutesByTo { '/absolute': typeof AbsoluteRoute '/linkProps': typeof LinkPropsRoute '/relative': typeof RelativeRoute + '/validateLinkOptions': typeof ValidateLinkOptionsRoute '/params/$paramsPlaceholder': typeof ParamsParamsPlaceholderRoute '/search/searchPlaceholder': typeof SearchSearchPlaceholderRoute } @@ -87,6 +95,7 @@ export interface FileRoutesById { '/absolute': typeof AbsoluteRoute '/linkProps': typeof LinkPropsRoute '/relative': typeof RelativeRoute + '/validateLinkOptions': typeof ValidateLinkOptionsRoute '/params/$paramsPlaceholder': typeof ParamsParamsPlaceholderRoute '/search/searchPlaceholder': typeof SearchSearchPlaceholderRoute } @@ -99,6 +108,7 @@ export interface FileRouteTypes { | '/absolute' | '/linkProps' | '/relative' + | '/validateLinkOptions' | '/params/$paramsPlaceholder' | '/search/searchPlaceholder' fileRoutesByTo: FileRoutesByTo @@ -109,6 +119,7 @@ export interface FileRouteTypes { | '/absolute' | '/linkProps' | '/relative' + | '/validateLinkOptions' | '/params/$paramsPlaceholder' | '/search/searchPlaceholder' id: @@ -119,6 +130,7 @@ export interface FileRouteTypes { | '/absolute' | '/linkProps' | '/relative' + | '/validateLinkOptions' | '/params/$paramsPlaceholder' | '/search/searchPlaceholder' fileRoutesById: FileRoutesById @@ -130,10 +142,18 @@ export interface RootRouteChildren { AbsoluteRoute: typeof AbsoluteRoute LinkPropsRoute: typeof LinkPropsRoute RelativeRoute: typeof RelativeRoute + ValidateLinkOptionsRoute: typeof ValidateLinkOptionsRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/validateLinkOptions': { + id: '/validateLinkOptions' + path: '/validateLinkOptions' + fullPath: '/validateLinkOptions' + preLoaderRoute: typeof ValidateLinkOptionsRouteImport + parentRoute: typeof rootRouteImport + } '/relative': { id: '/relative' path: '/relative' @@ -224,6 +244,7 @@ const rootRouteChildren: RootRouteChildren = { AbsoluteRoute: AbsoluteRoute, LinkPropsRoute: LinkPropsRoute, RelativeRoute: RelativeRoute, + ValidateLinkOptionsRoute: ValidateLinkOptionsRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/examples/react/large-file-based/src/routes/validateLinkOptions.tsx b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx new file mode 100644 index 00000000000..b92c73e4849 --- /dev/null +++ b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx @@ -0,0 +1,44 @@ +import { createFileRoute } from '@tanstack/react-router' +import * as React from 'react' +import { Link, linkOptions } from '@tanstack/react-router' +import type { + RegisteredRouter, + ValidateLinkOptions, +} from '@tanstack/react-router' + +export const Route = createFileRoute('/validateLinkOptions')({ + component: LinkPropsPage, +}) +// From the docs: https://tanstack.com/router/latest/docs/framework/react/guide/type-utilities#type-checking-link-options-with-validatelinkoptions +export interface HeaderLinkProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions = unknown, +> { + title: string + linkOptions: ValidateLinkOptions +} + +export function HeadingLink( + props: HeaderLinkProps, +): React.ReactNode { + return ( + <> +

{props.title}

+ + + ) +} + +function LinkPropsPage() { + return ( + + ) +} From ec72d3089e16065521d128343c8f24ee42de7604 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 10 Oct 2025 08:50:50 +0200 Subject: [PATCH 2/3] add repro --- .../src/routes/validateLinkOptions.tsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/examples/react/large-file-based/src/routes/validateLinkOptions.tsx b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx index b92c73e4849..1094ed48333 100644 --- a/examples/react/large-file-based/src/routes/validateLinkOptions.tsx +++ b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx @@ -30,15 +30,36 @@ export function HeadingLink( } function LinkPropsPage() { + const linkOptionsFromSomeOtherPlace = linkOptions({ + to: '/params/$param29', + params: { + param29: 'value29', + }, + }) return ( - + <> + {/* direct use, works */} + + {/* from a const from a linkOptions, works */} + + {/* from an inline linkOptions, breaks for some reason */} + + ) } From a752ed00574a7fd21b32b3ab1f79695c6ff95852 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 10 Oct 2025 09:36:49 +0200 Subject: [PATCH 3/3] mangle repro --- .../large-file-based/src/createRoutes.mjs | 14 +++-- .../src/routes/params/$paramsPlaceholder.tsx | 11 +--- .../src/routes/validateLinkOptions.tsx | 53 ++++++++++++++++--- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/examples/react/large-file-based/src/createRoutes.mjs b/examples/react/large-file-based/src/createRoutes.mjs index c86294b699b..59de3ff3d04 100644 --- a/examples/react/large-file-based/src/createRoutes.mjs +++ b/examples/react/large-file-based/src/createRoutes.mjs @@ -1,7 +1,7 @@ import { readFile, writeFile, mkdir } from 'fs/promises' import { existsSync } from 'fs' -const length = 100 +const length = 200 const main = async () => { const absolute = (await readFile('./src/routes/absolute.tsx')).toString() @@ -39,10 +39,14 @@ const main = async () => { const replacedRelative = relative.replaceAll('/relative', `/relative${y}`) const replacedSearch = search.replaceAll('searchPlaceholder', `search${y}`) const replacedParams = params.replaceAll('paramsPlaceholder', `param${y}`) - await writeFile(`./src/routes/(gen)/absolute${y}.tsx`, replacedAbsolute) - await writeFile(`./src/routes/(gen)/relative${y}.tsx`, replacedRelative) - await writeFile(`./src/routes/(gen)/search/search${y}.tsx`, replacedSearch) - await writeFile(`./src/routes/(gen)/params/$param${y}.tsx`, replacedParams) + // await writeFile(`./src/routes/(gen)/absolute${y}.tsx`, replacedAbsolute) + // await writeFile(`./src/routes/(gen)/relative${y}.tsx`, replacedRelative) + // await writeFile(`./src/routes/(gen)/search/search${y}.tsx`, replacedSearch) + // wait writeFile(`./src/routes/(gen)/params/$param${y}.tsx`, replacedParams) + await writeFile( + `./src/routes/(gen)/params/$fixedParam.$param${y}.$otherParam.tsx`, + replacedParams, + ) } } diff --git a/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx b/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx index 1442464a1c7..8144f2543ee 100644 --- a/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx +++ b/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx @@ -28,14 +28,5 @@ export const Route = createFileRoute('/params/$paramsPlaceholder')({ }) function ParamsComponent() { - return ( -
- -
- ) + return
} diff --git a/examples/react/large-file-based/src/routes/validateLinkOptions.tsx b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx index 1094ed48333..7bf88079907 100644 --- a/examples/react/large-file-based/src/routes/validateLinkOptions.tsx +++ b/examples/react/large-file-based/src/routes/validateLinkOptions.tsx @@ -16,6 +16,7 @@ export interface HeaderLinkProps< > { title: string linkOptions: ValidateLinkOptions + paramsToLink?: (id: string) => ValidateLinkOptions } export function HeadingLink( @@ -31,9 +32,10 @@ export function HeadingLink( function LinkPropsPage() { const linkOptionsFromSomeOtherPlace = linkOptions({ - to: '/params/$param29', + to: '/params/$fixedParam/$param1/$otherParam', params: { - param29: 'value29', + param1: 'value29', + fixedParam: 'value', }, }) return ( @@ -42,21 +44,56 @@ function LinkPropsPage() { {/* from a const from a linkOptions, works */} - {/* from an inline linkOptions, breaks for some reason */} + {/* from an inline linkOptions on a callback, breaks for some reason */} + {/* and without the linkOptions in the callback, type error */} + linkOptions({ + to: '/params/$fixedParam/$param99/$otherParam', + params: { + param99: id, + fixedParam: 'value', + otherParam: 'value', + }, + }) + } + /> + {/* and without the linkOptions in the callback, type error */} + ({ + to: '/params/$fixedParam/$param99/$otherParam', params: { - param29: 'value29', + param99: id, + fixedParam: 'value', + otherParam: 'value', }, })} />