76

How can I disable a <Link> in react-router, if its URL already active? E.g. if my URL wouldn't change on a click on <Link> I want to prevent clicking at all or render a <span> instead of a <Link>.

The only solution which comes to my mind is using activeClassName (or activeStyle) and setting pointer-events: none;, but I'd rather like to use a solution which works in IE9 and IE10.

Pipo
  • 5,623
  • 7
  • 36
  • 46

11 Answers11

75

You can use CSS's pointer-events attribute. This will work with most of the browsers. For example your JS code:

class Foo extends React.Component {
  render() {
    return (
      <Link to='/bar' className='disabled-link'>Bar</Link>
    );
  }
}

and CSS:

.disabled-link {
  pointer-events: none;
}

Links:

The How to disable HTML links answer attached suggested using both disabled and pointer-events: none for maximum browser-support.

a[disabled] {
    pointer-events: none;
}

Link to source: How to disable Link

Yasin
  • 1,150
  • 5
  • 19
  • 39
Denis Bubnov
  • 2,619
  • 5
  • 30
  • 54
  • 9
    It's important to note that the `pointer-events` technique will only disable clicks from a pointer device. It's still possible to select and activate the link with keyboard. – matharden Oct 08 '21 at 12:23
  • This approach is not safe. A user can trespass the disabled linked using dev tools. – Si Thu Nov 05 '22 at 09:52
51

This works for me:

<Link to={isActive ? '/link-to-route' : '#'} />
tretapey
  • 543
  • 4
  • 2
35

I'm not going to ask why you would want this behavior, but I guess you can wrap <Link /> in your own custom link component.

<MyLink to="/foo/bar" linktext="Maybe a link maybe a span" route={this.props.route} />

class MyLink extends Component {
    render () {
        if(this.props.route === this.props.to){
            return <span>{this.props.linktext}</span>
        }
        return <Link to={this.props.to}>{this.props.linktext}</Link>
    }
}

(ES6, but you probably get the general idea...)

dannyjolie
  • 10,959
  • 3
  • 33
  • 28
  • 5
    I can see why that would be useful. If the tag is replaced with a or a tag, then the link would turn into a bold item in a list of breadcrumbs when the link is clicked on. Clicking on another breadcrumb link would renable the tag for the previous click & apply the bold + disabled link state to the newly clicked link. – Clomp Jun 06 '16 at 19:01
  • With react-router v4, you need to use `this.props.history.location.pathname` instead of `this.props.route` – nbeuchat Jan 28 '18 at 02:19
  • 3
    I also suggest to use `{this.props.children}` instead of adding a new props `linktext`. This way, you can really use this component as a normal `Link` component. – nbeuchat Jan 28 '18 at 02:21
  • 1
    This solution is not accessible. You should pass `aria-disabled="true"` and either remove the `to` (though `Link` requires you to pass it) or use `preventDefault`. – SRachamim Jul 20 '20 at 08:39
  • Better use matchPath function from react-router library to check if the route is active. The aforementioned solution is not going to work in all cases, so don't rely on === comparison. – Umbrella Jan 01 '22 at 15:46
10

Another possibility is to disable the click event if clicking already on the same path. Here is a solution that works with react-router v4.

import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';

class SafeLink extends Component {
    onClick(event){
        if(this.props.to === this.props.history.location.pathname){
            event.preventDefault();
        }

        // Ensure that if we passed another onClick method as props, it will be called too
        if(this.props.onClick){
            this.props.onClick();
        }
    }

    render() {
        const { children, onClick, ...other } = this.props;
        return <Link onClick={this.onClick.bind(this)} {...other}>{children}</Link>
    }
}

export default withRouter(SafeLink);

You can then use your link as (any extra props from Link would work):

<SafeLink className="some_class" to="/some_route">Link text</SafeLink>
nbeuchat
  • 6,575
  • 5
  • 36
  • 50
2

All the goodness of React Router NavLink with the disable ability.

import React from "react"; // v16.3.2
import { withRouter, NavLink } from "react-router-dom"; // v4.2.2

export const Link = withRouter(function Link(props) {
  const { children, history, to, staticContext, ...rest } = props;
  return <>
    {history.location.pathname === to ?
      <span>{children}</span>
      :
      <NavLink {...{to, ...rest}}>{children}</NavLink>
    }
  </>
});
fsenart
  • 5,661
  • 2
  • 35
  • 54
  • `props` has no property `to` and no string index signature. – Frederik Krautwald Jun 13 '18 at 12:39
  • I'm not sure if I understand your remark but notice that `const Link...` is an HOC. Also props are the merge of `withRouter` given props and the usual `Link` props. `to` belongs to the props of the wrapped `Link`. PS: `NavLink` is a special version of `Link` and as such carries a `to` prop too. https://reacttraining.com/react-router/web/api/NavLink – fsenart Jun 13 '18 at 13:15
  • 1
    Ah sorry, the "typed" solution being out of scope, I prefer giving a more on purpose solution. But feel free to provide a typed version with your dialect of choice. – fsenart Jun 13 '18 at 13:27
2

Create a slim custom component like this below, you can also apply styling & css if you want as well maybe play with the opacity and pointer events none etc... or you can set the "to" to null when disabled from props

type Props = { disabled?: boolean;} & LinkProps; 

const CustomLinkReactRouter = (props: Props) => {
    const { disabled, ...standardProps } = props;
    return <Link {...standardProps} onClick={e => disabled && e.preventDefault()}/> 
}
export default CustomLinkReactRouter;
Rick Penabella
  • 339
  • 2
  • 10
1

React Router's Route component has three different ways to render content based on the current route. While component is most typically used to show a component only during a match, the children component takes in a ({match}) => {return <stuff/>} callback that can render things cased on match even when the routes don't match.

I've created a NavLink class that replaces a Link with a span and adds a class when its to route is active.

class NavLink extends Component {
  render() {
    var { className, activeClassName, to, exact, ...rest } = this.props;
    return(
      <Route
        path={to}
        exact={exact}
        children={({ match }) => {
          if (match) {
            return <span className={className + " " + activeClassName}>{this.props.children}</span>;
          } else {
            return <Link className={className} to={to} {...rest}/>;
          }
        }}
      />
    );
  }
}

Then create a navlink like so

<NavLink to="/dashboard" className="navlink" activeClassName="active">

React Router's NavLink does something similar, but that still allows the user to click into the link and push history.

Wesss
  • 95
  • 7
1

Based on nbeuchat's answer and component - I've created an own improved version of component that overrides react router's Link component for my project.

In my case I had to allow passing an object to to prop (as native react-router-dom link does), also I've added a checking of search query and hash along with the pathname

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Link as ReactLink } from 'react-router-dom';
import { withRouter } from "react-router";

const propTypes = {
  to: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
  location: PropTypes.object,
  children: PropTypes.node,
  onClick: PropTypes.func,
  disabled: PropTypes.bool,
  staticContext: PropTypes.object
};

class Link extends Component {
  handleClick = (event) => {
    if (this.props.disabled) {
      event.preventDefault();
    }

    if (typeof this.props.to === 'object') {
      let {
        pathname,
        search = '',
        hash = ''
      } = this.props.to;
      let { location } = this.props;

      // Prepend with ? to match props.location.search
      if (search[0] !== '?') {
        search = '?' + search;
      }

      if (
        pathname === location.pathname
        && search === location.search
        && hash === location.hash
      ) {
        event.preventDefault();
      }
    } else {
      let { to, location } = this.props;

      if (to === location.pathname + location.search + location.hash) {
        event.preventDefault();
      }
    }

    // Ensure that if we passed another onClick method as props, it will be called too
    if (this.props.onClick) {
      this.props.onClick(event);
    }
  };

  render() {
    let { onClick, children, staticContext, ...restProps } = this.props;
    return (
      <ReactLink
        onClick={ this.handleClick }
        { ...restProps }
      >
        { children }
      </ReactLink>
    );
  }
}

Link.propTypes = propTypes;

export default withRouter(Link);
Benji
  • 946
  • 10
  • 12
0

Another option to solve this problem would be to use a ConditionalWrapper component which renders the <Link> tag based on a condition.

This is the ConditionalWrapper component which I used based on this blog here https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2:

const ConditionalWrapper = ({ condition, wrapper, children }) =>
    condition ? wrapper(children) : children;

export default ConditionalWrapper

This is how we have used it:

const SearchButton = () => {
    const {
        searchData,
    } = useContext(SearchContext)

    const isValid = () => searchData?.search.length > 2

    return (<ConditionalWrapper condition={isValid()}
                                wrapper={children => <Link href={buildUrl(searchData)}>{children}</Link>}>
            <a
                className={`ml-auto bg-${isValid()
                    ? 'primary'
                    : 'secondary'} text-white font-filosofia italic text-lg md:text-2xl px-4 md:px-8 pb-1.5`}>{t(
                    'search')}</a>
        </ConditionalWrapper>
    )
}

This solution does not render the Link element and avoids also code duplication.

gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
0
const [isActive, setIsActive] = useState(true); 


<Link to={isActive ? '/link-to-route' :  null} />

you can try this, this worked for me.

Kishan Bharda
  • 5,446
  • 3
  • 30
  • 57
-8

If it fits your design, put a div on top of it, and manipulate the z-index.