T
TanStack12mo ago
like-gold

Getting proper `Link` types for own components?

Love the new additions and documentation for createLink(), thanks for the great work! We have a few different button components, e.g. PillButton, FlatButton, etc. and it supports a tag prop that can be set to any component which will be used to render the button. We've been using it by passing a link:
<PillButton
tag={Link}
variant="accent"
from={fromRoute}
to="./$action"
params={{
action: 'activate',
}}
children="Activate"
/>
<PillButton
tag={Link}
variant="accent"
from={fromRoute}
to="./$action"
params={{
action: 'activate',
}}
children="Activate"
/>
Where the type for the button is inferred as such (simplified implementation):
type ValidButtonTag = React.ElementType

function PillButton<Tag extends ValidButtonTag = 'button'>(
props,
{ tag?: Tag }
) {
const { tag = 'button', ...restProps } = props
const Component = tag
return <Component {...restProps} />
}
type ValidButtonTag = React.ElementType

function PillButton<Tag extends ValidButtonTag = 'button'>(
props,
{ tag?: Tag }
) {
const { tag = 'button', ...restProps } = props
const Component = tag
return <Component {...restProps} />
}
This works at runtime, but the Link types are never fully inferred and it struggles with resolving params/search. The typing is fully correct if we create a "Link" variation with createLink() as such:
export const PillButtonLink = createLink(
React.forwardRef((props: PillButtonProps<'a'>, ref: any) => (
<PillButton {...props} ref={ref} tag="a" />
)),
)
export const PillButtonLink = createLink(
React.forwardRef((props: PillButtonProps<'a'>, ref: any) => (
<PillButton {...props} ref={ref} tag="a" />
)),
)
But we ideally want to consume the Buttons as they are, and not have to create a "Link" component variation for each and one of them. Any idea how we can have TS correctly infer the Link props a la createLink() but with our own components?
1 Reply
genetic-orange
genetic-orange12mo ago
Hello. Can you provide a minimal reproduction with one of the examples? Theres a few challenges around the approach that you want. If PillButton does not share the same type parameters as Link then you won't get proper type safety because TSR cannot narrow to the types of the particular route. Exposing these type parameters is something we want to avoid. Its hard to maintain and we can break them So this is where abstractions like createLink come in because they return a component with the same type parameters as Link and you do not have to maintain them. I'm not totally sure why using PillButton as opposed to PillButtonLink is better here, can you explain why consuming PillButton is better than PillButtonLink? There might be some other abstraction on the type level if we think it is worth it but for now createLink is the main way

Did you find this page helpful?