Missing isActive prop on NavLink of React Router v6

Viewed 7530

NavLink component of react-router-dom package supports adding extra logic for determining whether the link is active. At least this was supported in older version. Here is link to documentation: https://reactrouter.com/web/api/NavLink/isactive-func

Code snippet:

<NavLink
  to="/events/123"
  isActive={(match, location) => {
    if (!match) {
      return false;
    }

    // only consider an event active if its event id is an odd number
    const eventID = parseInt(match.params.eventID);
    return !isNaN(eventID) && eventID % 2 === 1;
  }}
>
  Event 123
</NavLink>

It seems like isActive property is not supported anymore in version 6. What are other options to achieve the same thing?

3 Answers

NavLink activeClassName prop does not exist anymore. With that prop you could pass a class for that specific Link to be modified with some CSS to show that it's become active, like this:

export function Header() {
  return (
    <header>
      <ul>
        <li>
          <NavLink activeClassName="active" to="/about" />
        </li>
        <li>
          <NavLink activeClassName="active" to="/profile" />
        </li>
      </ul>
    </header>
  )
}

With V6 you have to manually do that and you can by passing a function to the className prop we have on React, like this:

export function Header() {
  return (
    <header>
      <ul>
        <li>
          <NavLink className={(navData) => navData.isActive ? "active" : "" } to="/about" />
        </li>
        <li>
          <NavLink className={(navData) => navData.isActive ? "active" : "" } to="/profile" />
        </li>
      </ul>
    </header>
  )
}

From the source code of react-router-dom v6.0.0-beta.0, isActive now seems to be automatic.

 /**
 * A <Link> wrapper that knows if it's "active" or not.
 */
export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
  function NavLinkWithRef(
    {
      'aria-current': ariaCurrentProp = 'page',
      activeClassName = 'active',
      activeStyle,
      caseSensitive = false,
      className: classNameProp = '',
      end = false,
      style: styleProp,
      to,
      ...rest
    },
    ref
  ) {
    let location = useLocation();
    let path = useResolvedPath(to);

    let locationPathname = location.pathname;
    let toPathname = path.pathname;
    if (!caseSensitive) {
      locationPathname = locationPathname.toLowerCase();
      toPathname = toPathname.toLowerCase();
    }

    let isActive = end
      ? locationPathname === toPathname
      : locationPathname.startsWith(toPathname);

    let ariaCurrent = isActive ? ariaCurrentProp : undefined;
    let className = [classNameProp, isActive ? activeClassName : null]
      .filter(Boolean)
      .join(' ');
    let style = { ...styleProp, ...(isActive ? activeStyle : null) };

    return (
      <Link
        {...rest}
        aria-current={ariaCurrent}
        className={className}
        ref={ref}
        style={style}
        to={to}
      />
    );
  }
);

Here

let isActive = end
          ? locationPathname === toPathname
          : locationPathname.startsWith(toPathname);

So to achieve the same you need to use your own style

And I made a MenuLink component which wraps Link and supports isActive props typed like this:

isActive?: (location: Location, path: Path) => boolean

Component looks like this:

import React, { forwardRef } from 'react'
import { Link, NavLinkProps, useLocation, useResolvedPath } from 'react-router-dom'
import { Location, Path } from 'history'

type TMenuLinkProps = NavLinkProps & {
  isActive?: (location: Location, path: Path) => boolean
}

const MenuLink = forwardRef<HTMLAnchorElement, TMenuLinkProps>(({ to, isActive, className: classNameProp = '', ...rest }, ref) => {
  let location = useLocation()
  let path = useResolvedPath(to)

  let locationPathname = location.pathname
  let toPathname = path.pathname

  let isLinkActive = isActive ? isActive(location, path) : locationPathname.startsWith(toPathname)

  const className = [classNameProp, isLinkActive ? 'active' : null].filter(Boolean).join(' ')
  return <Link {...rest} ref={ref} className={className} to={to} />
})

export default MenuLink

Related