admin管理员组

文章数量:1271801

I am using Next.js and want to add an active class to a nav link when the page it links to matches the url. But I also want it to be active when the url is deeper than only the page.

For example, a nav link to /register would be "active" for:

  • /register
  • /register/sign-up

I am using Next.js and want to add an active class to a nav link when the page it links to matches the url. But I also want it to be active when the url is deeper than only the page.

For example, a nav link to /register would be "active" for:

  • /register
  • /register/sign-up
Share Improve this question edited Jul 30, 2020 at 0:20 Luke Taylor 9,5909 gold badges60 silver badges96 bronze badges asked Feb 20, 2020 at 16:05 react-dev-in-trainingreact-dev-in-training 831 silver badge4 bronze badges 4
  • What is next.js? obviously a JavaScript file, but it could literally contain anything, how is anyone supposed to help based on the little information provided in your post? – SPlatten Commented Feb 20, 2020 at 16:06
  • 7 @SPlatte. nextjs. I thought it was obvious with it being so well known in the industry. – react-dev-in-training Commented Feb 20, 2020 at 16:08
  • Not to anyone not familiar with react. Have you tried embedding the URI in an iframe? – SPlatten Commented Feb 20, 2020 at 16:14
  • No. I think that would be overkill. I have used this and it works fine (flaviocopes./nextjs-active-link), But only for one layer. It doesn't work for deeper segmented urls @SPlatten – react-dev-in-training Commented Feb 20, 2020 at 16:32
Add a ment  | 

4 Answers 4

Reset to default 7

Here's a NavLink ponent I wrote for Next.js. It has the same API as Link, with the addition of two props:

  • an activeClassName that will be applied to the link when it matches
  • (optional) exact - prevents matching “deeper” routes)
import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';

export default function NavLink({ href, as, exact, activeClassName, children, ...props }) {
  const { asPath } = useRouter();
  // Normalize and split paths into their segments
  const segment = (p) => new URL(p, 'http://example.').pathname.split('/').filter(s => s);
  const currentPath = segment(asPath);
  const targetPath = segment(as || href);
  // The route is active if all of the following are true:
  //   1. There are at least as many segments in the current route as in the destination route
  //   2. The current route matches the destination route
  //   3. If we're in “exact" mode, there are no extra path segments at the end
  const isActive = currentPath.length >= targetPath.length
    && targetPath.every((p, i) => currentPath[i] === p)
    && (!exact || targetPath.length === currentPath.length);

  const child = React.Children.only(children);
  const className = ((child.props.className || '') + ' ' + (isActive ? activeClassName : '')).trim();

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

It's possible to make this simpler and more robust by relying on the route-matching functionality from the path-to-regexp module, which is already a dependency of Next.js:

import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { pathToRegexp } from 'path-to-regexp';

export default function NavLink({ href, as, exact, activeClassName, children, ...props }) {
  const { asPath } = useRouter();
  const isActive = pathToRegexp(as || href, [], { sensitive: true, end: !!exact }).test(asPath);

  const child = React.Children.only(children);
  const className = ((child.props.className || '') + ' ' + (isActive ? activeClassName : '')).trim();

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

Not sure about previous versions, but for Next.JS 14.*, it's much simpler to implement.

'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
 
export default function MyNavBar() {
  const segment = useSelectedLayoutSegment()
 
  return (
     <div>
        <Link className={segment == null   ? styles.active : "")} href="/">    Home</Link>
        <Link className={segment == 'info' ? styles.active : "")} href="/info">Info</Link>
   </div>
  )}

You can pare the segment var to the Link entry href you're on, or to null for home.

Note that this forces you to make the navBar a client ponent.

Note also that there's a way to look at more than one navigation layer. This is explained in the documentation as well.

See the documentation here: https://nextjs/docs/app/api-reference/functions/use-selected-layout-segment

I built on an example in the nextjs docs and a little bit of what's in the react-router-dom source to e up with this

import { useRouter } from "next/router";
import Link, { LinkProps } from "next/link";
import React, { useState, useEffect } from "react";

type Props = LinkProps & {
  className?: string;
  activeClassName: string;
  children?:
    | React.ReactNode
    | ((props: { isActive: boolean }) => React.ReactNode);
};

export default function NavLink({
  children,
  activeClassName,
  className,
  ...props
}: Props) {
  const { asPath, isReady } = useRouter();
  const [putedClassName, setComputedClassName] = useState(className);

  // Dynamic route will be matched via props.as
  // Static route will be matched via props.href
  const linkPathname = new URL(
    (props.as || props.href) as string,
    location.href
  ).pathname;

  // Using URL().pathname to get rid of query and hash
  const activePathname = new URL(asPath, location.href).pathname;

  const isActive = linkPathname === activePathname;

  useEffect(() => {
    // Check if the router fields are updated client-side
    if (isReady) {
      const newClassName = isActive
        ? `${className} ${activeClassName}`.trim()
        : className;

      if (newClassName !== putedClassName) {
        setComputedClassName(newClassName);
      }
    }
  }, [
    asPath,
    isReady,
    props.as,
    props.href,
    activeClassName,
    className,
    putedClassName,
    isActive,
  ]);

  return (
    <Link className={putedClassName} {...props}>
      {typeof children === "function" ? children({ isActive }) : children}
    </Link>
  );
}


If you're using Nextjs AppRouter 14* (don't know if it works with 13) and you have nested routes here is how to deal with it. Example:

  • if you've a route like this: /dashboard/contacts/ or /dashboard/contacts/new, useSelectedLayoutSegment will give you: contacts as a segment
  • if the route is dashboard you'll have null
import { usePathname, useSelectedLayoutSegment } from "next/navigation"

const pathname = usePathname()
const segment = useSelectedLayoutSegment()

const isActive = segment
  ? href.toString().includes(segment)
  : pathname === href

return (
  <Link href={href} className={cn("flex items-center px-4 py-2 text-gray-500 dark:text-gray-200 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700", 
    isActive && "text-primary border bg-gray-100 dark:border-gray-600 dark:text-primary dark:bg-gray-700")}>
    <Icon className="h-5 w-5 mr-3" />
    <span>{title}</span>
  </Link>
)

Hope it helps

本文标签: javascriptAdding active class for nav link in NextjsStack Overflow