Target Active Link when the route is active in Next.js

ReactjsReact Routernext.js

Reactjs Problem Overview


How to target the active Link in Next.js like they way we do it in React-Router-4? Meaning, give the active link a class when its route is active?

Reactjs Solutions


Solution 1 - Reactjs

A simple solution based on the useRouter hook:

import Link from "next/link";
import { useRouter } from "next/router";


export const MyNav = () => {

  const router = useRouter();

  return (
    <ul>
      <li className={router.pathname == "/" ? "active" : ""}>
        <Link href="/">home</Link>
      </li>
      <li className={router.pathname == "/about" ? "active" : ""}>
        <Link href="/about">about</Link>
      </li>
    </ul>
  );
};

You could also use router.asPath instead of router.pathname if you want to include the url query parameters. This can be useful if you want to handle anchor tags such as /#about.

Solution 2 - Reactjs

First, you need to have a component called Link, with temporary attribute activeClassName

import { useRouter } from 'next/router'
import PropTypes from 'prop-types'
import Link from 'next/link'
import React, { Children } from 'react'

const ActiveLink = ({ children, activeClassName, ...props }) => {
  const { asPath } = useRouter()
  const child = Children.only(children)
  const childClassName = child.props.className || ''

  // pages/index.js will be matched via props.href
  // pages/about.js will be matched via props.href
  // pages/[slug].js will be matched via props.as
  const className =
    asPath === props.href || asPath === props.as
      ? `${childClassName} ${activeClassName}`.trim()
      : childClassName

  return (
    <Link {...props}>
      {React.cloneElement(child, {
        className: className || null,
      })}
    </Link>
  )
}

ActiveLink.propTypes = {
  activeClassName: PropTypes.string.isRequired,
}

export default ActiveLink

Then have a navigation bar with created component Link and css selector :active to differentiate between active and inactive link.

import ActiveLink from './ActiveLink'

const Nav = () => (
  <nav>
    <style jsx>{`
      .nav-link {
        text-decoration: none;
      }
      .active:after {
        content: ' (current page)';
      }
    `}</style>
    <ul className="nav">
      <li>
        <ActiveLink activeClassName="active" href="/">
          <a className="nav-link">Home</a>
        </ActiveLink>
      </li>
      <li>
        <ActiveLink activeClassName="active" href="/about">
          <a className="nav-link">About</a>
        </ActiveLink>
      </li>
      <li>
        <ActiveLink activeClassName="active" href="/[slug]" as="/dynamic-route">
          <a className="nav-link">Dynamic Route</a>
        </ActiveLink>
      </li>
    </ul>
  </nav>
)

export default Nav

After that, you can implement the navigation bar to your page:

import Nav from '../components/Nav'

export default () => (
  <div>
    <Nav />
    <p>Hello, I'm the home page</p>
  </div>
)

The key of how does this work is located inside component Link, we compare the value of router.pathname with attribute href from the Link, if the value match the other then put specific className to make the link looks activated.

Reference: here

Solution 3 - Reactjs

Another minimal version which supports as prop:

import Link from "next/link";
import {withRouter} from "next/router";
import {Children} from "react";
import React from "react";

export default withRouter(({router, children, as, href, ...rest}) => (
   <Link {...rest} href={href} as={as}>
      {React.cloneElement(Children.only(children), {
         className: (router.asPath === href || router.asPath === as) ? `active` : null
      })}
   </Link>
));

Solution 4 - Reactjs

If you want to use an anchor Link try this version of @Rotareti 's code:

import Link from "next/link";
import { useRouter } from "next/router";


export const MyNav = () => {

  const router = useRouter();

  return (
    <ul>
      <li className={router.asPath == "/#about" ? "active" : ""}>
        <Link href="#about">about</Link>
      </li>
    </ul>
  );
}`;

Solution 5 - Reactjs

Typescript version:

import React from 'react'
import Link, { LinkProps } from 'next/link'
import { useRouter } from 'next/router'

export interface NavLinkProps extends LinkProps {
  children: React.ReactElement
}

export function NavLink({ children, href, ...props }: NavLinkProps) {
  const router = useRouter()
  return (
    <Link href={href} {...props}>
      {router.pathname === href ? React.cloneElement(children, { 'data-active': true }) : children}
    </Link>
  )
}

Note that I'm not cloning the child unless necessary.

Solution 6 - Reactjs

This is my solution. I tokenise the href and the asPath props and then loop through to match them.

You can choose an exact link (default)

<ActiveLink href='/events'>
    <a href='/page'>Page</a>
</ActiveLink>

Or a fuzzy link (matches /events) with the fuzzy prop

<ActiveLink fuzzy href='/events/id'>
    <a href='/events/id'>Event</a>
</ActiveLink>

Here's the component

import React from 'react';
import NextLink from 'next/link';
import { useRouter } from 'next/router';

const ActiveLink = ({ fuzzy = false, href, children }) => {
const router = useRouter();
let className = children.props.className || '';

const hrefTokens = href.substr(1).split('/');
const pathTokens = router.asPath.substr(1).split('/');

let matched = false;
for (let i = 0; i < hrefTokens.length; i++) {
    if (hrefTokens[i] === pathTokens[i]) {
    matched = true;
    break;
    }
 }

 if ((!fuzzy && router.asPath === href) || (fuzzy && matched)) {
    className = `${className} active`;
  }

  return (
    <NextLink href={href}>
      {React.cloneElement(children, { className })}
    </NextLink>
  );
};

export default ActiveLink;

Solution 7 - Reactjs

Here is another version of ActiveLink with NextJS (see the result image below)

import { withRouter } from 'next/router';
import PropTypes from 'prop-types';
import React from 'react';

const ActiveLink = ({ router, href, isLeftSideBar = false, children }) => {
  const isCurrentPath = router.pathname === href || router.asPath === href;

  const handleClick = (e) => {
    e.preventDefault();
    router.push(href);
  };

  (function prefetchPages() {
    if (typeof window !== 'undefined') router.prefetch(router.pathname);
  })();

  const theme =
    settings.theme === THEMES.LIGHT && isLeftSideBar ? '#e65100' : '#ffeb3b';
  const color = isCurrentPath ? theme : '';

  return (
    <a
      href={href}
      onClick={handleClick}
      style={{
        textDecoration: 'none',
        margin: 16,
        padding: 0,
        fontWeight: isCurrentPath ? 'bold' : 'normal', // I left mine all bold
        fontSize: 17,
        color: isLeftSideBar ? '#e65100' : '#ffeb3b',
      }}>
      {children}
    </a>
  );
};

ActiveLink.propTypes = {
  href: PropTypes.string.isRequired,
  children: PropTypes.any,
};

export default withRouter(ActiveLink);

Call it anywhere

<ActiveLink href='/signup'> Sign Up </ActiveLink>

Result:

enter image description here

Solution 8 - Reactjs

Here is a solution that also works if URL-parameters are present and checks if a sub-page is active. Based on the answers by Darryl RN and Saman Mohamadi

It works as a drop-in replacement for the NextJS link component and adds the classes "active" and "active-sub" if the route or the route of a subpage is active.

Create a file called Link.js or whatever you like:

import { withRouter } from "next/router";
import Link from "next/link";
import React, { Children } from "react";

export default withRouter(({ router, children, as, href, activeClassName, activeSubClassName, ...rest }) => {
  const child = Children.only(children);
  const childClassName = child.props.className || "";
  // remove URL parameters
  const sanitizedPath = router.asPath.split("#")[0].split("?")[0];
  // activeClassName and activeSubClassName are optional and default to "active" and "active-sub"
  const activeClass = activeClassName || "active";
  const activeSubClass = activeSubClassName || "active-sub";
  // remove trailing slash if present
  href = href && href !== "/" && href.endsWith("/") ? href.slice(0, -1) : href;
  as = as && as !== "/" && as.endsWith("/") ? as.slice(0, -1) : as;
  // check if the link or a sub-page is active and return the according class name
  const activityClassName = sanitizedPath === href || sanitizedPath === as ? activeClass : sanitizedPath.startsWith(href + "/") || sanitizedPath.startsWith(as + "/") ? activeSubClass : "";
  // combine the child class names with the activity class name
  const className = `${childClassName} ${activityClassName}`.trim();
  return (
    <Link href={href} as={as} {...rest}>
      {React.cloneElement(child, {
        className: className || null,
      })}
    </Link>
  );
});

import it in your files via

import Link from "./Link.js";

or with any name you like

import ActiveLink from "./Link.js";

and use it as you would use the NextJS "Link" component (next/link):

<Link href="/home">
  <a className="link-classname">Home</a>
</Link>

it will default to the class names "active" and "active-sub", but you can set custom class names:

<Link href="/home" activeClassName="my-active-classname" activeSubClassName="another-classname">
  <a className="link-classname">Home</a>
</Link>

If you don't need one of the active classes put a space in the string:

<Link href="/home" activeSubClassName=" ">
  <a className="link-classname">Home</a>
</Link>

Solution 9 - Reactjs

//NavItem Wrapper
import { useRouter } from 'next/router'
import React from 'react'

const ActiveNav = ({ path, children }) => {
    const router = useRouter();
    const className = router.asPath === `/${path}` ? "active" : '';
    return (
        <div className={className}>
            {children}
        </div>
    )
}

export default ActiveNav

// in another file

import NavbarItem from 'path of ActiveNav component';

const { Header, Content, Footer } = Layout;

const LayoutComponent = (props) => {

  return (
   
      <>
        <nav className="navigation">
          <NavbarItem path="">
            <div className="nav-items">
              <Link href="/">
                <a>Home</a>
              </Link>
            </div>
          </NavbarItem>
          <NavbarItem path="category/game">
            <div className="nav-items">
              <Link href="/category/game">
                <a>Game</a>
              </Link>
            </div>
          </NavbarItem>
          
        </nav>
      <>
      
  )
}

export default LayoutComponent

> add the style file and import it (Globally or in the Active Nav component)

 .navigation > .active{
   color:green;
   font:bold;
   // customize according to need
}

Solution 10 - Reactjs

I create a component in typescript

import { UrlObject } from "url";


interface ActiveLinkProps {
  activeClassName?: string;
  href: string | UrlObject;
}

// children is the <a>, prop is the "href"
const ActiveLink: React.FC<ActiveLinkProps> = ({ children, ...props }) => {
  const router = useRouter();
  // this will make sure i m passing only one child so i will access the its props easily
  const child = Children.only(children) as React.ReactElement;
  let className = child.props ? child.props.className : "";

  if (router.asPath === props.href && props.activeClassName) {
    className = `${className} ${props.activeClassName}`;
  }

  delete props.activeClassName;

  return (
    <Link href={props.href}>{React.cloneElement(child, { className })}</Link>
  );
};

then use it like this

<ActiveLink activeClassName="active" href={href}>
  <a className={`nav-link port-navbar-link ${className}`}>{title}</a>
</ActiveLink>

Solution 11 - Reactjs

Just put an a tag in it...

<Link href={href}>
  <a className='text-red-400 active:text-red-800'>{children}</a>
</Link>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRubyView Question on Stackoverflow
Solution 1 - ReactjsRotaretiView Answer on Stackoverflow
Solution 2 - ReactjsDarryl RNView Answer on Stackoverflow
Solution 3 - ReactjsSaman MohamadiView Answer on Stackoverflow
Solution 4 - ReactjsMatthias KrämerView Answer on Stackoverflow
Solution 5 - ReactjsDominicView Answer on Stackoverflow
Solution 6 - ReactjssidonaldsonView Answer on Stackoverflow
Solution 7 - ReactjsDiaMaBoView Answer on Stackoverflow
Solution 8 - ReactjsFelixView Answer on Stackoverflow
Solution 9 - Reactjssadab khanView Answer on Stackoverflow
Solution 10 - ReactjsYilmazView Answer on Stackoverflow
Solution 11 - ReactjsDevmentorLiveView Answer on Stackoverflow