How to wrap a Link component with type safety?

364 Views Asked by At

There is a Link component in @tanstack/react-router. I want to nest (wrap) this component into my own component(s).

How can I do this and keep the type-safety which it provides?

This is example of the structure

interface ContainerProps {
  to: // What type should be here?
  param: // What type should be here?
}


function Container (props: ContainerProps) {
  return <WrappedLink to={props.to} params={props.params} />
}

interface WrapperProps {
 // What should be here?
}

function WrappedLink(props: PropsWithChildren<WrapperProps>) {
  return <Link {...props}>{props.children}</Link>
}

This is how it should be used

function Page() {
  const params = page1.useParams()

  return <Container to={page2.to} params={params.foo} /> // NOTE: TypeScript should autosuggest what values are available
}

This is component hierarchy for simplicity

Page (pass `to` & `params`)
- Container (pass `to` & `params`)
 - Wrapper (pass `to` & `params`)
  - Link

P.S. I checked other posts here on StackOverflow, but didn't find a solution :(

2

There are 2 best solutions below

0
Roman Mahotskyi On BEST ANSWER

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.

0
lecstor On

I assume due to updates to the lib the previous answer is no longer squiggly-free. After much shotgunning I stumbled upon LinkComponent and that seems to be working for me..

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

export const Link: LinkComponent<"a"> = (props) => <TsrLink {...props} />;

Screenshot of link implementation and usage demonstrating type safety

Edit..

I wanted to add more props for styling so went back and managed to update the "MakeLinkOptions" answer provided by Tanner Linsley in a Github ticket..

It does only appear to be link-specific props that are included though, so children, style, and probably others still need to be added.

Also, Link from the router seems to be ok with absolute urls, but my wrapper has lost that and complains.

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

type ExtraProps = {
  size?: "sm" | "md" | "lg";
  children: ReactNode;
};

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",
    }}
  />
);