How to set activeClassName for wrapper element of Link or IndexLink in react-router?

37,865

Solution 1

You need to enclose your <li> as a router aware component:

import { Link, IndexLink } from 'react-router'

class NavItem extends React.Component {
  render () {
    const { router } = this.context
    const { index, onlyActiveOnIndex, to, children, ...props } = this.props

    const isActive = router.isActive(to, onlyActiveOnIndex)
    const LinkComponent = index ? Link : IndexLink

    return (
      <li className={isActive ? 'active' : ''}>
        <LinkComponent {...props}>{children}</LinkComponent>
      </li>
    )
  }
}

Usage:

<ul>
  <NavItem to='/' index={true}>Home</NavItem>
  <NavItem to='/a'>A</NavItem>
</ul>

I took inspration from the react-router-bootstrap module, https://github.com/react-bootstrap/react-router-bootstrap/blob/master/src/LinkContainer.js. I didn't test it though so let me know how it goes.

Solution 2

The other answers don't seem to work in React Router v4. Here's how you can do it:

import React, {PropTypes} from 'react'
import {Route, Link} from 'react-router-dom'
import styles from './styles.less';

export default function NavItem({children, to, exact}) {
    return (
        <Route path={to} exact={exact} children={({match}) => (
            <li className={match ? styles.activeRoute : null}>
                <Link to={to}>{children}</Link>
            </li>
        )}/>
    )
}

NavItem.propTypes = {
    to: PropTypes.string.isRequired,
    exact: PropTypes.bool,
    children: PropTypes.node.isRequired,
};

Solution 3

/**
 * A navigation component
 */
import React, { Component } from 'react'
import { Link, IndexLink, withRouter } from 'react-router'

import styles from './styles.scss'

class NavItem extends Component {
  render () {
    const { router } = this.props
    const { index, to, children, ...props } = this.props

    let isActive
    if( router.isActive('/',true) && index ) isActive = true
    else  isActive = router.isActive(to)
    const LinkComponent = index ?  IndexLink : Link

    return (
      <li className={isActive ? 'active' : ''}>
        <LinkComponent to={to} {...props}>{children}</LinkComponent>
      </li>
    )
  }
}

NavItem = withRouter(NavItem)

export default NavItem

Usage:

<ul className="nav nav-tabs"> 
  <NavItem to='/home' index={true} >Home</NavItem>
  <NavItem to='/about'>About</NavItem>
</ul>

Solution 4

{/* Make sure that `location` is injected into this component */}
<ul className="nav navbar-nav">
  <li className={location.pathname === '/' && 'active'}>
    <Link to='/'>
      Home page
    </Link>
  </li>
  <li className={location.pathname.startsWith('/about') && 'active'}>
    <Link to='/about'>
      About us
    </Link>
  </li>
</ul>

Solution 5

In stead of using <Link />, I use <NavLink /> and It works as well.

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

//.....

export default class AppNav extends Component {

    render (){
        return (
                <header>
                    <ul className="main-nav">
                        <li><NavLink activeClassName={"active"} exact={true} to="/">Home</NavLink></li>
                        <li><NavLink activeClassName={"active"} to="/about">About</NavLink></li>
                        <li><NavLink activeClassName={"active"} to="/courses">Courses</NavLink></li>
                    </ul>
                </header>
        );
    }
}
Share:
37,865
abekenza
Author by

abekenza

Software Engineer: iOS, Android, Java, JavaEE, MySQL

Updated on July 26, 2022

Comments

  • abekenza
    abekenza almost 2 years

    I am new to the ReactJS world, and would like to know how can I pass active class name to the <li> element instead of <a>(Link) element.

    Now I have this kind of code. The anchor class changes when clicked.

    <li><IndexLink to='/' activeclassName='active'>A</IndexLink></li>
    <li><Link to='/b' activeclassName='active'>B</Link></li>
    <li><Link to='/c' activeclassName='active'>C</Link></li>
    

    But I would like to get something similar to:

    <li activeclassName='active'><IndexLink to='/'>A</IndexLink></li>
    <li activeclassName='active'><Link to='/b'>B</Link></li>
    <li activeclassName='active'><Link to='/c'>C</Link></li>
    

    Thanks in advance

  • Nick
    Nick over 7 years
    Although it looks similar to Marc's answer on first glance, this answer worked better for me. withRouter seems to be the way in current versions to get access to the router.
  • CLaff
    CLaff over 7 years
    This worked for me but since i was using a IndexLink i had make this one change: else isActive = router.isActive(to, true) . I had to add true to IsActive function or else isActive always returned true for '/'.
  • Kushal Shah
    Kushal Shah over 7 years
    Depending on your setup, you may need NavLi.contextTypes = { router: React.PropTypes.object }; export default NavLi;
  • Uchenna
    Uchenna about 7 years
    Easiest answer.
  • Tran Dinh Khanh
    Tran Dinh Khanh about 7 years
    Thank you for this. I would add ...props to params and use it in <Link to={to} {...props}>{children}</Link> for more flexible.
  • GajananB
    GajananB about 7 years
    You may need to ensure your component gets the context e.g. router: PropTypes.object.isRequired
  • Travis Watson
    Travis Watson almost 7 years
    Instead of getting router through the context, use the withRouter HOC from react-router package to get the router via props. This will also update the link's active status when the page changes, since otherwise React has no way to know if the isActive has changed.
  • dano
    dano over 6 years
    This fixed my issue. I wonder what the difference is between them that makes it work?
  • Sagiv b.g
    Sagiv b.g over 6 years
    i would not use the ternary operator and instead use this <li className={match && styles.activeRoute}> more terse.
  • mpen
    mpen over 6 years
    @Sag1v I don't like using that syntax anymore. I got bit when it evaluated to 0 and I had a stray 0 in my UI. Can create even worse bugs if you're not careful.
  • Sagiv b.g
    Sagiv b.g over 6 years
    @mpen i know what you mean, though in this case a 0 would produce the same effect won't it?
  • Derek
    Derek over 6 years
    Did not worked: router is undefined for me, not found in this.props :-(
  • Tmh
    Tmh over 6 years
    Worked for me, no need of any workarounds, Should mark it as the best answer.
  • Tobbe
    Tobbe over 6 years
    Updated jsfiddle for the answer @mpen gave above: jsfiddle.net/69z2wepo/99537
  • Abhisek Mishra
    Abhisek Mishra about 6 years
    Is it setting the class of parent <li> element ? For me it is just setting the class of <a> that is created from <NavLink>.
  • Goran Jakovljevic
    Goran Jakovljevic over 5 years
    This wasnt the question. He asked about parent class.
  • Bastian Voigt
    Bastian Voigt over 5 years
    withRouter(...) does not work for me. this.context.router is still undefined :(
  • Zerquix18
    Zerquix18 over 5 years
    This component doesn't re-render when the route changes, so it'll only become active for the first render
  • mpen
    mpen over 5 years
    @Zerquix18 Sounds like a bug in your code. Did you put a PureComponent somewhere further up the tree perhaps? This NavItem is an SFC, there isn't any logic to prevent a re-render.
  • Aaron C
    Aaron C almost 4 years
    The OP wants to add the class to the parent element of the Link, not the Link itself.
  • guijob
    guijob about 3 years
    for react router v5 you should use ` <li className={match.path === to ? styles.activeRoute : null}>`