T
TanStack4w ago
sensitive-blue

Defining custom link prop types without breaking type inference

I'm migrating a next codebase to tanstack router, and we have a lot of components that can act either as a link or a button (something like Props = { onClick: () => void } | { href: UrlObject }. I'm trying to replace the link part with tanstack router links, so I tried the following:
type SomeButton = { onClick: () => void } | Pick<LinkComponentProps, "to" | "search" | "params">
type SomeButton = { onClick: () => void } | Pick<LinkComponentProps, "to" | "search" | "params">
However, picking seems to break type inference for search and params (I can pass any search and param defined across the entire app). Anyone got an idea how I could achieve this?
12 Replies
adverse-sapphire
adverse-sapphire4w ago
Custom Link | TanStack Router React Docs
While repeating yourself can be acceptable in many situations, you might find that you do it too often. At times, you may want to create cross-cutting components with additional behavior or styles. Yo...
adverse-sapphire
adverse-sapphire4w ago
Type Utilities | TanStack Router React Docs
Most types exposed by TanStack Router are internal, subject to breaking changes and not always easy to use. That is why TanStack Router has a subset of exposed types focused on ease of use with the in...
sensitive-blue
sensitive-blueOP4w ago
Ohh very nice thank you, I'll look into it! Pretty sure you also responded to my questions about rounter context on github discussions, thanks again for that! Hmm, seems like this works for the button|link case, but I'm still stuck on the following:
function getLinkProps(target: "a" | "b"): WhatTypeShouldThisBe {
switch (target) {
case "a":
return { to: "/a/$firstId", params: { firstId: "..." } }
case "b":
return { to: "/b/$secondId", params: { secondId: "..." } }
}
}
function getLinkProps(target: "a" | "b"): WhatTypeShouldThisBe {
switch (target) {
case "a":
return { to: "/a/$firstId", params: { firstId: "..." } }
case "b":
return { to: "/b/$secondId", params: { secondId: "..." } }
}
}
adverse-sapphire
adverse-sapphire4w ago
sensitive-blue
sensitive-blueOP4w ago
Damn, how did I not see this in the docs Thanks! Another quick question, the following should work from what I understand:
type Props = Omit<MenuItemProps<"a">, "component" | "href">

export const LinkMenuItem = createLink(
forwardRef<HTMLAnchorElement, Props>(function MenuItemLinkInner(props, ref) {
return <MenuItem component="a" {...props} ref={ref} />
}),
)

export type LinkMenuItemProps = LinkComponentProps<typeof LinkMenuItem>

const TestComponent = <TOptions = unknown,>(props: {
linkOptions: ValidateLinkOptions<RegisteredRouter, TOptions>
}) => <LinkMenuItem {...props.linkOptions}>Hello</LinkMenuItem>
type Props = Omit<MenuItemProps<"a">, "component" | "href">

export const LinkMenuItem = createLink(
forwardRef<HTMLAnchorElement, Props>(function MenuItemLinkInner(props, ref) {
return <MenuItem component="a" {...props} ref={ref} />
}),
)

export type LinkMenuItemProps = LinkComponentProps<typeof LinkMenuItem>

const TestComponent = <TOptions = unknown,>(props: {
linkOptions: ValidateLinkOptions<RegisteredRouter, TOptions>
}) => <LinkMenuItem {...props.linkOptions}>Hello</LinkMenuItem>
However, the last line throws an error on the <LinkMenuItem> props:
Type 'LinkComponentReactProps<"a"> & ...' is not assignable to type 'LinkComponentReactProps<ForwardRefExoticComponent<Omit<Props, "ref"> & RefAttributes<HTMLAnchorElement>>>'. [2322]
Type 'LinkComponentReactProps<"a"> & ...' is not assignable to type 'LinkComponentReactProps<ForwardRefExoticComponent<Omit<Props, "ref"> & RefAttributes<HTMLAnchorElement>>>'. [2322]
Any idea what the problem is? I suspect the forwardRef messes something up, but I have no idea what I would have to omit from the props to fix that Also, do I need to forwardRef when using createLink? It's explicitly done in the docs, so I assumed so
adverse-sapphire
adverse-sapphire4w ago
cc @Chris Horobin
sensitive-blue
sensitive-blueOP4w ago
I can try to get a minimal repro going if that helps
adverse-sapphire
adverse-sapphire4w ago
sure!
sensitive-blue
sensitive-blueOP4w ago
Is there a base project I can use? Like a playground example or something?
adverse-sapphire
adverse-sapphire4w ago
adverse-sapphire
adverse-sapphire4w ago
StackBlitz
Router Basic File Based Example - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
sensitive-blue
sensitive-blueOP4w ago
Can't reproduce with html elements, so it must be something about material-ui. I'll try to narrow it down Ok, I'm stumped. The exact same code with the exact same library versions (react, react-dom, mui) doesn't throw the error in the playground. I get a similar error intermittently, but it disappears when reloading the playground. Here's the exact error I'm getting locally:
'LinkComponentProps<
"a",
RouterCore<
Route<
any, "/", "/",
string, "__root__",
(search: Record<string, unknown>) => GlobalSearch,
{}, {}, AnyContext, AnyContext, {},
undefined, RootRouteChildren, FileRouteTypes
>,
"never",
false,
RouterHistory,
Record<string, any>
>,
InferFrom<TOptions, string>,
InferTo<TOptions>,
InferMaskFrom<TOptions>,
InferMaskTo<TOptions>
>'

is not assignable to type
'LinkComponentReactProps<
ForwardRefExoticComponent<
Omit<Props, "ref"> & RefAttributes<HTMLAnchorElement>
>
>'. [2322]
'LinkComponentProps<
"a",
RouterCore<
Route<
any, "/", "/",
string, "__root__",
(search: Record<string, unknown>) => GlobalSearch,
{}, {}, AnyContext, AnyContext, {},
undefined, RootRouteChildren, FileRouteTypes
>,
"never",
false,
RouterHistory,
Record<string, any>
>,
InferFrom<TOptions, string>,
InferTo<TOptions>,
InferMaskFrom<TOptions>,
InferMaskTo<TOptions>
>'

is not assignable to type
'LinkComponentReactProps<
ForwardRefExoticComponent<
Omit<Props, "ref"> & RefAttributes<HTMLAnchorElement>
>
>'. [2322]
I'll try a local repro next Ok, can't reproduce it in a clean repo either. I have no idea what else I can try. I'll take care of the rest of the migration first, then come back to this. Open to any ideas!

Did you find this page helpful?