Navigation using a Link's prop.href duplicates the Router basename

Viewed 435

I'm trying to figure out custom react-router navigation when the enclosing BrowserRouter component defines a basename property.

I am using the Link component property to render a link as a higher-order Button component (in this case from the Kendo UI library). The onClick reads the href prop to push navigation via history. However, the resulting URL repeats the base pathname.

In this example (React 17, router 5.2), if you navigate to /area/simple and click the Link (which has no child component), it takes you back to /area, but if you navigate to /area/complex and click the Button, it takes you to /area/area. (Inspecting prop.href shows that it is /area.)

Is history push the wrong way to navigate here? How does the anchor-based Link manage to navigate correctly?

// imports omitted, Root is like App, it's a single-spa convention

const Root = () => {
    return (
        <Router basename="/area">

            <Route exact path="/">
                <h2>You Are Home</h2>
            </Route>

            <Route exact path="/simple">
                <h2>Plain Link Component</h2>
                <Link to="/">Go Home</Link>
            </Route>

            <Route exact path="/complex">
                <h2>Link with Child</h2>
                <Link to="/" component={ HomeButton }>
            </Route>

        </Router>
    );
};

const HomeButton = (props) => {
    return <LinkButton icon="home" {...props}>Home</LinkButton>
};

// normally a reusable component in a separate file
const LinkButton = (props) => {
    const { push } = useHistory();
    return (
        <Button onClick={() => push(props.href)} {...props}>{props.children}</Button>
    )
};

export default Root;

(I'm not interested in suggestions to re-style the Link as a button -- we're trying to stick to the built-in themed styles from the Kendo UI library, and replicating those accurately creates future maintenance headaches.)

1 Answers

The props for a custom Link render component aren't well documented, so we have to look at the source code for the Link component to see what's going on.

Solution

Your custom link component is called with props href and navigate.

The href is for compatibility with the a tag more than anything else. The default render component LinkAnchor doesn't actually use the href for navigation at all. It passes it to the DOM but the a tag that it creates intercepts clicks and handles them itself.

The navigate prop is what you actually want to use. You simply call this function in response to a click.

const LinkButton = (props) => {
  return (
      <Button {...props} onClick={props.navigate} />
  )
};

More Info

Check out this section of the Link component source code:

const href = location ? history.createHref(location) : "";
const props = {
  ...rest,
  href,
  navigate() {
    const location = resolveToLocation(to, context.location);
    const method = replace ? history.replace : history.push;

    method(location);
  }
};

You can see how the navigate prop is created and what it does. You can also see that the href is created from a separate method than the location. The href needs to look normal to the user so it is the entire path to page including the basename "/area". Meawhile, history.push expects a relative location which does not include the basename.

Related