How to wrap tanstack/router Link with another component while keeping type safety

1.2k Views Asked by At

I would like to wrap the Link component from Tanstack Router with another component and be able to pass this component the props that are needed to be passed to the Link component.

I.e. I want to make a reusable that uses wraps an external button with the Tanstack Router Link

Something like this

import { Link, LinkOptions } from '@tanstack/router'

interface ButtonLinkProps {
  // how do I make this type safe, it results to `string | undefined` instead of valid routes I have defined
  to: LinkOptions['to']
  children: string
}

export function ButtonLink(props: ButtonLinkProps) {
  return (
    <Link to={props.to} params={{}} search={{}}>
      <Button component='span'>{props.children}</Button>
    </Link>
  )
}

When using the Link component directly the value passed to the to prop can only be one of the defined routes. However I'm not sure how define the to prop in my own defined component to achieve the same result.

// How do I achieve the same type safety I would get if I would use Link directly
<ButtonLink to='/'>Link</ButtonLink>
3

There are 3 best solutions below

1
Sjiep On

Found the following solution that works for me on version 0.0.1-beta.104, assuming my ButtonLink component in the OP:

interface ButtonLinkProps {
  to: MakeLinkOptions['to']
  children: string
}

or if we simply want all props that can be provided to a Link

interface ButtonLinkProps extends MakeLinkOptions {
  children: string
}
0
Roman Mahotskyi On

This is an example of how it can be used with "@tanstack/react-router": "^1.2.2"

import { AnyRoute, Link, LinkProps, RegisteredRouter, RoutePaths } from "@tanstack/react-router";
import { ReactNode } from "react";

export function CustomLink<
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
  TFrom extends RoutePaths<TRouteTree> | string = string,
  TTo extends string = '',
  TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom, TMaskTo extends string = ''
>({
  linkProps,
  children,
}: {
  linkProps: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & React.RefAttributes<HTMLAnchorElement>
  children: ReactNode
}) {
  return <Link {...linkProps}>{children}</Link>
}

Then, it can be used like

function Main() {
  return (
    <CustomLink linkProps={{ to: "/$param", params: { foo: "foo" } }}>
      Click here
    </CustomLink>
  )
}

P.S. Honestly, I don't know what all these types mean, but it was suggested on the React-Router Discord channel.

This answer is a also duplicated here.

0
lecstor On

my latest and best solution..

import { Link as TsrLink, LinkOptions, RegisteredRouter } from "@tanstack/react-router";

type RT = RegisteredRouter["routeTree"];

export const Link = <TTo extends string = ".">({
  size,
  ...props
}: LinkOptions<RT, "/", TTo> & ExtraProps) => (
  <TsrLink
    {...sxp(sx.base, size && sx[size])}
    {...props}
    activeProps={{
      className: "font-bold",
    }}
  />
);