Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to extends Link component from router file #150

Closed
FrancoRATOVOSON opened this issue Feb 23, 2024 · 9 comments
Closed

How to extends Link component from router file #150

FrancoRATOVOSON opened this issue Feb 23, 2024 · 9 comments

Comments

@FrancoRATOVOSON
Copy link

Sorry, discussion is not enabled in this repos so I opened an issue.

Context

I shadcn-ui for my app and I style my links the way it is explained in the (doc)[https://ui.shadcn.com/docs/components/button#as-child], here's the code :

import { Link as RouterLink } from 'react-router-dom'

import { Button, buttonVariants } from 'ui/components'
import { VariantProps } from 'ui/utils'

interface LinkProps
  extends React.ComponentProps<typeof RouterLink>,
    VariantProps<typeof buttonVariants> {}

function Link({ size, variant = 'link', ...props }: LinkProps) {
  return (
    <Button asChild size={size} variant={variant}>
      <RouterLink {...props} />
    </Button>
  )
}

As you can see, I had to import the link component from react-router-dom, because when I use the one provided by the route.ts file like this :

import { Button, buttonVariants } from 'ui/components'
import { VariantProps } from 'ui/utils'

import { Link as RouterLink } from '../router'

interface LinkProps
  extends React.ComponentProps<typeof RouterLink>,
    VariantProps<typeof buttonVariants> {}

function Link({ size, variant = 'link', ...props }: LinkProps) {
  return (
    <Button asChild size={size} variant={variant}>
      <RouterLink {...props} />
    </Button>
  )
}

I get a typescript error with the {...props}

Cannot assign '{}' to the type 'IntrinsicAttributes & (LinkProps & ({ to: "/"; params?: undefined; } | { to: "/customers"; params?: undefined; } | { to: "/customers/:id"; params: { id: string; }; } | ... 5 more ... | { ...; }))'

And yes, when I tried to see, if I : const props: React.ComponentProps<typeof RouterLink>, the type of props is indeed { }.

So how can I extends my prop type to match the prop type of this component ?

@oedotme
Copy link
Owner

oedotme commented Feb 27, 2024

Hey @FrancoRATOVOSON, could you please share a StackBlitz example with your usage of shadcn/ui and generouted? I'd like to try adding the types in this context and make sure it works.

@FrancoRATOVOSON
Copy link
Author

Hey @FrancoRATOVOSON, could you please share a StackBlitz example with your usage of shadcn/ui and generouted? I'd like to try adding the types in this context and make sure it works.

https://stackblitz.com/edit/vitejs-vite-edpmge?file=src%2Fcomponents%2Flink.tsx

@oedotme
Copy link
Owner

oedotme commented Mar 7, 2024

@FrancoRATOVOSON I've exported helper types to simplify the custom usage. I tried to access the StackBlitz demo, but seems broken at the moment — here's an example:

import { Link as RouterLink } from 'react-router-dom'
import { LinkProps } from '@generouted/react-router/client'

import { Params, Path } from '../../src/router'

type VariantProps = { variant: 'solid' | 'outline' }

export function Link<P extends Path>({ variant, ...props }: LinkProps<P, Params> & VariantProps) {
  return (
    <Button variant={variant}>
      <RouterLink {...props} />
    </Button>
  )
}

;<Link to="/login" variant="solid">Login</Link>
;<Link to="/not-valid">Not valid</Link> // type-error

Hope that helps.

@braydenbabbitt
Copy link

braydenbabbitt commented Mar 18, 2024

Just used this to add a NakedLink component in my project that extends Link and adds css to remove default anchor styles. Super helpful! Thanks!

@FrancoRATOVOSON
Copy link
Author

@FrancoRATOVOSON I've exported helper types to simplify the custom usage. I tried to access the StackBlitz demo, but seems broken at the moment — here's an example:

import { Link as RouterLink } from 'react-router-dom'
import { LinkProps } from '@generouted/react-router/client'

import { Params, Path } from '../../src/router'

type VariantProps = { variant: 'solid' | 'outline' }

export function Link<P extends Path>({ variant, ...props }: LinkProps<P, Params> & VariantProps) {
  return (
    <Button variant={variant}>
      <RouterLink {...props} />
    </Button>
  )
}

;<Link to="/login" variant="solid">Login</Link>
;<Link to="/not-valid">Not valid</Link> // type-error

Hope that helps.

This doesnt consider params and just past the to given as props.

@oedotme
Copy link
Owner

oedotme commented Apr 3, 2024

@FrancoRATOVOSON I'm not sure I understand what you mean, could you please elaborate on that?

Also, would be helpful if you provide a StackBlitz example as the previous one you shared seems broken.

@briandunn
Copy link

briandunn commented Apr 12, 2024

@oedotme I wonder if you can provide an example of using the Link from the generated router file and extending that? I like the way that component takes a Path and Params, but I can't figure out how to customize it.

TLDR: Typescript error on line 20 https://stackblitz.com/edit/github-oenxul?file=src%2Fpages%2F_app.tsx

example:

import { Link, Path, Params } from "@/router";
import classNames from "classnames";
import type { LinkProps } from "@generouted/react-router/client";

export type NavigationItemProps<P extends Path> = LinkProps<P, Params> & {
  active?: boolean;
  linkClass?: string;
  label: string;
  icon: React.ReactNode;
};

export default function OpenNavigationItem<P extends Path>({ active = false, params, linkClass, icon, to }: NavigationItemProps<P>) {
  return (
    <Link
      params={params}
      to={to}
      className={classNames(
        "hover:!bg-gray-200 flex justify-center items-center h-14 w-14 rounded-full",
        { "bg-sys-brand-secondary-container": active },
        linkClass
      )}
    >
      {icon}
    </Link>
  );
}

I expected this to work, since it's just extending the LinkProps type. instead I get:

error TS2322: Type '{ children: ReactNode; params: { sectionId: string; } | { sectionId: string; } | undefined; to: "/" | "/a" | "/b" | "/c" | ... 6 more ... | "/d/:id"; className: string; }' is not assignable to type 'IntrinsicAttributes & LinkProps<"/" | "/a" | "/b" | "/c" | "/d" | ... 5 more ... | "/e/:id", Params>'.
    Types of property 'to' are incompatible.
        Type '"/"' is not assignable to type '"/d/:id"'.

Sorry route names are obfuscated.

@oedotme
Copy link
Owner

oedotme commented Apr 12, 2024

@briandunn @FrancoRATOVOSON As the params prop is conditional, we'd need to pass all link props grouped.

Following the shorter example you've provided, this would fail:

const PrettyLink = <P extends Path>({ to, params }: LinkProps<P, Params>) => (
  <Link to={to} params={params} data-pretty />
)

We should instead pass all props directly:

const PrettyLink = <P extends Path>({ ...props }: LinkProps<P, Params>) => (
  <Link {...props} data-pretty />
)

To combine other props with the Link props — as the longer example, it seems that TypeScript infers props differently when you destructure other props before ...props:

type Props = { active: boolean }

const PrettyLink = <P extends Path>({ active, ...props }: Props & LinkProps<P, Params>) => {
  return <Link {...props} data-pretty />
}

In this case instead, we can either use props.active directly (which is my preference, that's why I haven't encounter issues extending the props 🙂) or destructure the external props later in the component:

type Props = { active: boolean }

const PrettyLink = <P extends Path>(props: Props & LinkProps<P, Params>) => {
  // props.active | const { active } = props
  return <Link {...props}  data-pretty />
}

<PrettyLink to="/posts/:id" params={{ id: "xyz" }} />  

Third possibility would be to allocate a namespace for original link props:

type Props = { active: boolean }

const PrettyLink = <P extends Path>({ active, link }: Props & { link: LinkProps<P, Params> }) => {
  return <Link {...link} data-pretty />
}

<PrettyLink link={{ to: '/posts/:id', params: { id: 'xyz' } }} />  

Hope that helps!

@oedotme oedotme closed this as completed May 13, 2024
@oedotme
Copy link
Owner

oedotme commented May 13, 2024

Closing for inactivity. Feel free to reopen if you guys still have problems with the types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants