router icon indicating copy to clipboard operation
router copied to clipboard

Custom Links not getting correct href value in the DOM

Open andyswanepoel opened this issue 1 year ago • 11 comments

Which project does this relate to?

Router

Describe the bug

I'm using I followed the docs for creating @chakra-ui/react and was following the docs for creating a custom link.

The routing all works as expected, however when hovering over the links in the browser, I noticed the URL was that of the path for the route I was on.

I did also quickly test is with the exact set up with Material UI as shown in the docs with the same issue.

Your Example Website or App

https://stackblitz.com/edit/github-ygojrn?file=CustomLink.tsx

Steps to Reproduce the Bug or Issue

  1. Go to https://stackblitz.com/edit/github-ygojrn?file=CustomLink.tsx
  2. Visit the home page of the App.
  3. Open the dev tool and look at the link to the About page
  4. See the href is /

Expected behavior

I expect that the URL in the browser would reflect the destination path/route.

Screenshots or Videos

https://github.com/user-attachments/assets/e7957293-017d-4d70-8057-0461fd0ae9ef

Platform

  • OS: macOS
  • Browser: Chrome
  • Version: 129.0.6668.90

Additional context

No response

andyswanepoel avatar Oct 09 '24 02:10 andyswanepoel

Confirmed this is not a Chakra-specific issue. This is happening with MUI as well. https://stackblitz.com/edit/github-ygojrn-jjltmm?file=src%2Froutes%2Findex.tsx

SeanCassiere avatar Oct 09 '24 04:10 SeanCassiere

I have the same issue with a fairly simple custom link component. Navigation and prefetching on hover intent etc works as normal.

interface NavLinkProps extends Omit<RACLinkProps, 'className' | 'slot' | 'children' | 'style'> {
  children?: React.ReactNode
}

export const NavLink = createLink(
  React.forwardRef<React.ElementRef<typeof Link>, NavLinkProps>(
    ({ isDisabled, children, ...rest }, forwardedRef) => {
      const ref = useObjectRef(forwardedRef)

      const { isHovered, hoverProps } = useHover({ isDisabled, ...rest })
      const { isPressed, linkProps } = useLink({ isDisabled, ...rest }, ref)

      return (
        <Link
          {...mergeProps(hoverProps, linkProps, rest)}
          ref={ref}
          data-hovered={isHovered || undefined}
          data-pressed={isPressed || undefined}
          className={navItemStyles({ isDisabled, isHovered, isPressed })}
          activeOptions={{ includeSearch: false }}
        >
          {children}
        </Link>
      )
    },
  ),
)

Image

christofferbergj avatar Oct 09 '24 15:10 christofferbergj

I’ve altered the reproduction to use the approach I’m using at work and it works fine:

https://stackblitz.com/edit/github-ygojrn-pwpojs?file=CustomLink.tsx,src%2Froutes%2Findex.tsx,src%2Fmain.tsx

I’ve removed forwardRef because it’s a pain but I don’t think that’s the issue. Feel free to re-add it.

TkDodo avatar Oct 09 '24 15:10 TkDodo

I've added the react-aria-components example issue to @TkDodo's reproduction.

https://stackblitz.com/edit/github-ygojrn-wrompg?file=src%2Froutes%2Findex.tsx

Here the CustomRACLink links will get the current page as the href.

christofferbergj avatar Oct 10 '24 07:10 christofferbergj

I don’t think you’re supposed to render a <Link> from the TanStack Router inside createLink, because that’s what createLink does. Look at how it’s implemented (with types stripped):

export function createLink(Comp) {
  return React.forwardRef(function CreatedLink(props, ref) {
    return <Link {...props} _asChild={Comp} ref={ref} />
  })
}

that means inside of createLink, you’re supposed to render your Link abstraction, like a ReactAriaLink or a ChakraLink, not a TanStackRouter Link. This is what my solution does and it works well with all kinds of link implementations.

TkDodo avatar Oct 10 '24 07:10 TkDodo

That makes sense, but then the docs are confusing to me, or I'm simply just reading it completely wrong.

Image

For anyone coming across this using React Aria Components: Intent preload is lost when using the RAC Link component, due to it not supporting onMouseEnter and onMouseLeave. Their components are using onHoverStart and onHoverEnd instead.

https://react-spectrum.adobe.com/blog/building-a-button-part-2.html#the-usehover-hook

Thanks @TkDodo !

christofferbergj avatar Oct 10 '24 08:10 christofferbergj

yeah seems like the docs need to be updated

TkDodo avatar Oct 10 '24 09:10 TkDodo

I'll take a crack at it asap.

christofferbergj avatar Oct 10 '24 09:10 christofferbergj

@SeanCassiere @TkDodo

PR for revised documentation: docs: add to createLink documentation #2506

christofferbergj avatar Oct 10 '24 12:10 christofferbergj

Oh glad I found this, been cracking my head round this for the last hour or so.

I liked the approach as described in the current docs, as you can add some default options to the Link component this way. The new approach looses that functionality.

Maybe it can be done like so:

const CustomLink = createLink((props) => <a {...props} />, {
  activeOptions: { exact: false },
  activeProps: { ['data-active']: '' }
})

Pagebakers avatar Oct 11 '24 15:10 Pagebakers

@Pagebakers

You can still do that. I have tried to indicate that in the revised docs by including the Link-specific prop: preload={'intent'} before spreading in other props.

import * as React from 'react'
import { createLink, LinkComponent } from '@tanstack/react-router'

interface BasicLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  // Add any additional props you want to pass to the anchor element
}

const BasicLinkComponent = React.forwardRef<HTMLAnchorElement, BasicLinkProps>(
  (props, ref) => {
    return (
      <a ref={ref} {...props} className={'block px-3 py-2 text-blue-700'} />
    )
  },
)

const CreatedLinkComponent = createLink(BasicLinkComponent)

export const CustomLink: LinkComponent<typeof BasicLinkComponent> = (props) => {
  return (
    <CreatedLinkComponent 
      preload={'intent'} 
      activeOptions={{ exact: false }}
      // other Link-specific props.
      {...props} />
  )
}

christofferbergj avatar Oct 13 '24 17:10 christofferbergj

closing this now that the docs have been updated. please open a new issue if there are still open issues

schiller-manuel avatar Oct 21 '24 17:10 schiller-manuel

We haven't been able to get this to work with Mantine's Menu.Item component which can be a Link (or anchor) under the hood, if anyone else has gotten this to work (I tried TkDodo's solution but it seems to result in unrenderable HTML, something incorrectly the child of an anchor or something), I would be very grateful if they shared how.

komali2 avatar Sep 19 '25 07:09 komali2