T
TanStackโ€ข9mo ago
fascinating-indigo

Link + Pagination + Search Prop + Shadcn/UI

Hey there, I'm trying to build the pagination for my app and I struggle more than I should. ๐Ÿ˜… I use the pagination components from Shadcn/UI and I replaced the a element from PaginationLink with Link from @tanstack/react-router. It works as expected if I concatenate stuff, but if I try to use the search prop, I get a type error even though it works. Can anyone help me, please? Thanks!
No description
No description
No description
16 Replies
absent-sapphire
absent-sapphireโ€ข9mo 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...
fascinating-indigo
fascinating-indigoOPโ€ข9mo ago
Thanks for the reply! Not sure that is the solution though in my case. I updated the type and it seems to work now. Or am I wrong?
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
LinkProps &
LinkComponentProps<"a">;
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
LinkProps &
LinkComponentProps<"a">;
absent-sapphire
absent-sapphireโ€ข9mo ago
can you provide a complete minimal example by forking an existing router example on stackblitz?
fascinating-indigo
fascinating-indigoOPโ€ข9mo ago
sensitive-blue
sensitive-blueโ€ข8mo ago
Hey! I've stumbled upon this problem myself and I've managed to fix it thanks to the custom link component thingy as per the docs, but I still got a problem when it comes to passing Link props type to <PaginationPrevious> and <PaginationNext> components. Honestly, I'm not sure if it's even viable to do so. Would love to get some help/clarification on this topic. Here's the complete minimal reproduction of the issue, see line 65: https://stackblitz.com/edit/tanstack-router-qyn239bg?file=src%2Froutes%2Fposts.tsx @Manuel Schiller I'd appreciate if you could take a look at this ๐Ÿ™
Daniel Izdebski
StackBlitz
[Tanstack Router] Broken shadcn/ui Pagination Typesafety - StackBlitz
Forked example of Tanstack Router with broken typesafety of shadcn/ui pagination components
absent-sapphire
absent-sapphireโ€ข8mo ago
you could try using ValidateLinkOptions instead
sensitive-blue
sensitive-blueโ€ข8mo ago
Not really sure how to apply this in my example. I've did some more digging before you replied and I found that LinkComponentProps fixes the types but I'm not sure how valid this approach is. I've replaced:
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
with:
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, 'size'> &
LinkComponentProps
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, 'size'> &
LinkComponentProps
How valid is this type?
absent-sapphire
absent-sapphireโ€ข8mo ago
cc @Chris Horobin
foreign-sapphire
foreign-sapphireโ€ข8mo ago
Afk but i think this is possible with ValidateLinkOptions type PaginationLinkProps<TOptions> = ValidateLinkOptions<TOptions> & Pick<ButtonProps, 'size'>
sensitive-blue
sensitive-blueโ€ข8mo ago
I might be doing something wrong here with the TOptions but doesn't seem to be working. I'll patiently wait till you're back by your computer. In the meantime, will use the LinkComponentProps since at least I can keep on coding
foreign-sapphire
foreign-sapphireโ€ข8mo ago
Can you show an example of how you are using it?
sensitive-blue
sensitive-blueโ€ข8mo ago
Actually after more tries and the GitHub link shared by Manuel I've managed to make it work. Here's the diff: https://www.diffchecker.com/y0hcRaDK/
Diffchecker - Compare text online to find the difference between tw...
Diffchecker will compare text to find the difference between two text files. Just paste your files and click Find Difference!
sensitive-blue
sensitive-blueโ€ข8mo ago
But this casting props as any is just awful What exactly is the LinkComponentProps though? With that type, there's no need for casting props as any but I'm not comfortable using something that I'm not fully understanding so would appreciate some input here
foreign-sapphire
foreign-sapphireโ€ข8mo ago
LinkComponentProps is an internal type used for props of Link. Without type parameters there will be no narrowing to a specific route for params or search ValidateLinkOptions should get you the correct type safety with only one type parameter. It focuses more on type safety of a public interface, custom.Link for example but given generic functions/components it can be hard to eliminate all required type assertions The type assertions should only be required internally And as never can usually work if you dont like any
sensitive-blue
sensitive-blueโ€ข8mo ago
Makes sense. Couldn't get it to work with never nor unknown so I just stuck with casting props to PaginationLinkProps<TOptions> which makes sense type wise as I really dislike any. Thanks for the help! Here's the complete fix for anyone who will find this thread in the future:
import { Link, type ValidateLinkOptions } from '@tanstack/react-router'

type PaginationLinkProps<TOptions> = {
isActive?: boolean
} & Pick<ButtonProps, 'size'> &
ValidateLinkOptions<TOptions>

const PaginationLink = <TOptions,>({ className, isActive, size = 'icon', ...props }: PaginationLinkProps<TOptions>) => (
<Link
aria-current={isActive ? 'page' : undefined}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size
}),
className
)}
{...(props as ValidateLinkOptions<TOptions>)}
/>
)
PaginationLink.displayName = 'PaginationLink'

const PaginationPrevious = <TOptions,>({ className, ...props }: React.ComponentProps<typeof PaginationLink<TOptions>>) => (
<PaginationLink aria-label="Go to previous page" size="default" className={cn('gap-1 pl-2.5', className)} {...(props as PaginationLinkProps<TOptions>)}>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = 'PaginationPrevious'

const PaginationNext = <TOptions,>({ className, ...props }: React.ComponentProps<typeof PaginationLink<TOptions>>) => (
<PaginationLink aria-label="Go to next page" size="default" className={cn('gap-1 pr-2.5', className)} {...(props as PaginationLinkProps<TOptions>)}>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = 'PaginationNext'
import { Link, type ValidateLinkOptions } from '@tanstack/react-router'

type PaginationLinkProps<TOptions> = {
isActive?: boolean
} & Pick<ButtonProps, 'size'> &
ValidateLinkOptions<TOptions>

const PaginationLink = <TOptions,>({ className, isActive, size = 'icon', ...props }: PaginationLinkProps<TOptions>) => (
<Link
aria-current={isActive ? 'page' : undefined}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size
}),
className
)}
{...(props as ValidateLinkOptions<TOptions>)}
/>
)
PaginationLink.displayName = 'PaginationLink'

const PaginationPrevious = <TOptions,>({ className, ...props }: React.ComponentProps<typeof PaginationLink<TOptions>>) => (
<PaginationLink aria-label="Go to previous page" size="default" className={cn('gap-1 pl-2.5', className)} {...(props as PaginationLinkProps<TOptions>)}>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = 'PaginationPrevious'

const PaginationNext = <TOptions,>({ className, ...props }: React.ComponentProps<typeof PaginationLink<TOptions>>) => (
<PaginationLink aria-label="Go to next page" size="default" className={cn('gap-1 pr-2.5', className)} {...(props as PaginationLinkProps<TOptions>)}>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = 'PaginationNext'

Did you find this page helpful?