import Link from './Link'; // our version of link
export default () => (
<header className="Header">
<nav>
<Link activeClassName="active" href="/">
<a className="some-other-class">Home</a>
</Link>
<Link activeClassName="active" href="/about">
<a>About</a>
</Link>
<Link activeClassName="active" href="/contact">
<a>Contact</a>
</Link>
</nav>
</header>
);
-
-
Save remy/0dde38897d6d660f0b63867c2344fb59 to your computer and use it in GitHub Desktop.
import { withRouter } from 'next/router'; | |
import Link from 'next/link'; | |
import React, { Children } from 'react'; | |
const ActiveLink = ({ router, children, ...props }) => { | |
const child = Children.only(children); | |
let className = child.props.className || ''; | |
if (router.pathname === props.href && props.activeClassName) { | |
className = `${className} ${props.activeClassName}`.trim(); | |
} | |
delete props.activeClassName; | |
return <Link {...props}>{React.cloneElement(child, { className })}</Link>; | |
}; | |
export default withRouter(ActiveLink); |
In case someone only needs to match the first url segment, try this:
const ActiveLink = withRouter(({ router, children, ...props }) => (
<Link {...props}>
{React.cloneElement(Children.only(children), {
className:
`/${router.pathname.split("/")[1]}` === props.href ? `active` : null
})}
</Link>
));
In case someone using as
in links, just change pathname
with asPath
.
zeti/next
already have the use case with withRouter
HOC.
https://github.com/zeit/next.js/tree/canary/examples/using-with-router
Thanks a lot! Cleaned version of ActiveLink.
import { withRouter } from 'next/router';
import cx from 'classnames';
import Link from 'next/link';
import React, { Children } from 'react';
const ActiveLink = ({
router,
children,
href,
activeClassName,
...otherProps
}) => {
const child = Children.only(children);
const className = cx(child.props.className, {
[activeClassName]: router.pathname === href && activeClassName
});
return (
<Link href={href} {...otherProps}>
{React.cloneElement(child, { className })}
</Link>
);
};
What's that 'classnames' on second line? @emil-alexandrescu
Thank you! Here is my typescript version:
import cx from 'classnames';
import { WithRouterProps } from 'next/dist/client/with-router';
import NextLink from 'next/link';
import { withRouter } from 'next/router';
import { ReactElementLike } from 'prop-types';
import { Children, cloneElement, Component } from 'react';
interface LinkProps extends WithRouterProps {
href: string;
children: ReactElementLike;
activeClassName: string;
}
class Link<P extends LinkProps> extends Component<P> {
public render() {
const {
router,
children,
href,
activeClassName,
...otherProps
} = this.props;
const child = Children.only(children);
const active = this.props.router.pathname === href && activeClassName;
const className = cx(
child.props.className,
{ [activeClassName]: active }
);
return (
<NextLink href={this.props.href} {...otherProps}>
{cloneElement(child, { className })}
</NextLink>
);
}
}
export default withRouter(Link);
Here's a version using a Hook instead of a HOC.
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
const ActiveLink = ({ children, ...props }) => {
const router = useRouter();
const child = React.Children.only(children);
let className = child.props.className || "";
if (router.pathname === props.href && props.activeClassName) {
className = `${className} ${props.activeClassName}`.trim();
}
delete props.activeClassName;
return <Link {...props}>{React.cloneElement(child, { className })}</Link>;
};
Here's a version using a Hook and styled-components:
const ActiveLink = ({ children, ...props }) => {
const router = useRouter()
const child = React.Children.only(children)
return (
<Link {...props}>
{React.cloneElement(child, { active: router.pathname === props.href })}
</Link>
)
}
Using example:
<ActiveLink href="/path" prefetch>
<NavLink>Vikup auto</NavLink>
</ActiveLink>
NavLink components something like this:
const NavLink = styled.a`
color: ${({ active }) => active ? 'red' : 'black'};
`
Hey guys im having an issue with dynamic routing and this component.
I have a Nav component in components folder:
import Link from './Link';
export default () => (
<nav>
<style jsx>{`
.active {
color: red;
}
.nav-link {
text-decoration: none;
padding: 10px;
display: block;
}
`}</style>
<ul>
<li>
<Link activeClassName="active" href="/">
<a className="nav-link home-link">Home</a>
</Link>
</li>
<li>
<Link activeClassName="active" href="/about">
<a className="nav-link">About</a>
</Link>
</li>
<li>
<Link activeClassName="active" href="/post/[id]" as="/post/first">
<a>First Post</a>
</Link>
</li>
<li>
<Link activeClassName="active" href="/post/[id]" as="/post/second">
<a>Second Post</a>
</Link>
</li>
</ul>
</nav>
);
and my pages folder look like this:
pages/ |---- post/ | ---- [id]/ | ---- index.js |---- about.js |---- index.js
and the Index.js from [id] folder:
import {useRouter} from 'next/router';
import Nav from '../../../components/Nav';
const Post = () => {
const router = useRouter();
const {id} = router.query;
return (
<React.Fragment>
<Nav />
<h1>Post: {id}</h1>
</React.Fragment>
);
};
export default Post;
the problem it's that adds right the active class in al cases except if u reload in /post/[id], if so its return this error and don't add the active class:
Warning: Prop
className did not match. Server: "jsx-382284644" Client: "jsx-382284644 active"
in my Link component im comparin the asPath insted of href like this:
import {withRouter} from 'next/router';
import Link from 'next/link';
import React, {Children} from 'react';
const ActiveLink = ({router, children, as, ...props}) => {
const child = Children.only(children);
let className = child.props.className || null;
if (as) {
if (router.asPath === as && props.activeClassName) {
className = `${className !== null ? className : ''} ${props.activeClassName}`.trim();
}
} else {
if (router.pathname === props.href && props.activeClassName) {
className = `${className !== null ? className : ''} ${props.activeClassName}`.trim();
}
}
delete props.activeClassName;
return (
<Link as={as} {...props}>
{React.cloneElement(child, {className})}
</Link>
);
};
export default withRouter(ActiveLink);
A version with ES6 destructuring, proptypes and useRotuer hook:
import React from 'react';
import Link from 'next/link';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router';
const ActiveLink = ({ href, activeClassName, children }) => {
const router = useRouter();
const child = React.Children.only(children);
let className = child.props.className || '';
if (router.pathname === href && activeClassName) {
className = `${className} ${activeClassName}`.trim();
}
return <Link href={href}>{React.cloneElement(child, { className })}</Link>;
};
ActiveLink.propTypes = {
href: PropTypes.string,
activeClassName: PropTypes.string,
children: PropTypes.node.isRequired
};
ActiveLink.defaultProps = {
href: '',
activeClassName: ''
};
export default ActiveLink;
I do it like this:
const ActiveLink: NextPage<Props> = ({ children, href }) => {
const router = useRouter();
return (
<Link href={href} passHref>
<Anchor active={router.pathname === href}>{children}</Anchor>
</Link>
);
};
const Anchor = styled.a<{ active: boolean }>`
color: ${props => (props.active ? 'red' : 'blue')};
`;
If full control is needed just replace Link all together, from their docs:
import { useRouter } from 'next/router'
function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.pathname === href ? 'red' : 'black',
}
const handleClick = e => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default ActiveLink
new typescript version:
import { withRouter } from 'next/router';
const ActiveLink = ({ router, href, children }: any) => {
(function prefetchPages() {
if (typeof window !== 'undefined') {
router.prefetch(router.pathname);
}
})();
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
router.push(href);
};
const isCurrentPath = router.pathname === href || router.asPath === href;
return (
<a
href={href}
onClick={handleClick}
className="url-item"
style={{
fontWeight: isCurrentPath ? 'bold' : 'normal',
color: isCurrentPath ? '#F00' : '#fff'
}}
>
{children}
</a>
);
};
export default withRouter(ActiveLink);
I did something like this with typescript and tailwind css :
import { useRouter } from 'next/router'
import Link from 'next/link'
type Props = {
href: string
linkName: string
activeClassName?: string
} & typeof defaultProps
const defaultProps = {
activeClassName: 'text-green font-600',
}
export const NavLink = ({ href, linkName, activeClassName }: Props) => {
const router = useRouter()
return (
<Link href={href}>
<a className={router.pathname === href ? activeClassName : null}>
{linkName}
</a>
</Link>
)
}
NavLink.defaultProps = defaultProps
If you need support for dynamic routes ([slug], [id], [...param], [[...slug]], etc.) in Next.js. The following will check if the href contains a dynamic route and then check if the "router.asPath" is equal to the "props.as". When matched the "activeClassName" will be added to the link.
Thanks @remy!
import { useRouter } from "next/router";
import Link from "next/link";
import React, { Children } from "react";
const ActiveLink = ({ children, ...props }) => {
const router = useRouter();
const child = Children.only(children);
let className = child.props.className || "";
const isDynamicRoute = props.href.match(/^\/?\[{1,2}\.{0,3}[a-z]+\]{1,2}$/);
if (
router.pathname === props.href &&
!isDynamicRoute &&
props.activeClassName
) {
className = `${className} ${props.activeClassName}`.trim();
} else if (router.asPath === props.as && isDynamicRoute) {
className = `${className} ${props.activeClassName}`.trim();
}
delete props.activeClassName;
return <Link {...props}>{React.cloneElement(child, { className })}</Link>;
};
export default ActiveLink;
A version with ES6 destructuring, proptypes and useRotuer hook:
import React from 'react'; import Link from 'next/link'; import PropTypes from 'prop-types'; import { useRouter } from 'next/router'; const ActiveLink = ({ href, activeClassName, children }) => { const router = useRouter(); const child = React.Children.only(children); let className = child.props.className || ''; if (router.pathname === href && activeClassName) { className = `${className} ${activeClassName}`.trim(); } return <Link href={href}>{React.cloneElement(child, { className })}</Link>; }; ActiveLink.propTypes = { href: PropTypes.string, activeClassName: PropTypes.string, children: PropTypes.node.isRequired }; ActiveLink.defaultProps = { href: '', activeClassName: '' }; export default ActiveLink;
Thanks a lot, for use:
<NavLink href = "/path" activeClassName = "--active">
<a className="classes">
Home Page
</a>
</NavLink>
Hello , work for me .
find on SO https://stackoverflow.com/questions/53262263/target-active-link-when-the-route-is-active-in-next-js :
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>
);
};
my code :
<Link href="/">
<a className={router.pathname ==="/" ? styles.navlinkActive : styles.navlink}>
Accueil
</a>
</Link>
All these look like great solutions, but regrettably I cannot make any to work reliably with this navbar structure:
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import ActiveLink from '../CustomLink/ActiveLink';
export default function Navigation() {
return (
<Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
<ActiveLink activeClassName="active" href="/">
<Navbar.Brand>
<img src="/logo.svg" alt="WhiteQueen Logo" height={50} />
</Navbar.Brand>
</ActiveLink>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<ActiveLink activeClassName="active" href="/">
<Nav.Link>Home</Nav.Link>
</ActiveLink>
<ActiveLink activeClassName="active" href="/publications">
<Nav.Link>Publications</Nav.Link>
</ActiveLink>
<ActiveLink activeClassName="active" href="/links">
<Nav.Link>Links</Nav.Link>
</ActiveLink>
</Nav>
<Nav>
<ActiveLink activeClassName="active" href="/contact">
<Nav.Link>Contact</Nav.Link>
</ActiveLink>
</Nav>
</Navbar.Collapse>
</Navbar>
);
}
If I click on a link nested in a different <Nav>
(for example, "Contact") the highlighting of the previous one isn't removed...
Am I missing something obvious?
I rather have my own function that creates a pure next/link
component:
import Link from "next/link";
import {useRouter} from "next/router";
import styles from './Toolbar.module.css'
const Toolbar = () => {
const router = useRouter();
const activeLink = (path, content, activeClass = styles.active, normalClass = '') => {
let className = router.pathname === path ? activeClass : normalClass;
return <Link href={path}><a className={className}>{content}</a></Link>
}
return <div className={styles.toolbar}>
<nav>
{activeLink('/', 'Home')}
{activeLink('/about', 'About')}
</nav>
</div>
}
export default Toolbar;
If you need a more reusable function, you can pass the router:
import Link from "next/link"
const createActiveLink = (router, path, content, activeClass = 'active', normalClass = '') => {
let className = router.pathname === path ? activeClass : normalClass;
return <Link href={path}><a className={className}>{content}</a></Link>
}
export default createActiveLink
typed. 21/10/22
Thank you very much remy san XD
import { withRouter, NextRouter } from 'next/router';
import React, { ReactElement } from 'react';
import Link from 'next/link';
type Props = {
router: NextRouter;
children: ReactElement;
href: string;
activeClassName: string;
}
const ActiveLink = ({ router, children, ...props }: Props) => {
const child = children;
let className: string = child.props.className;
if (router.pathname == props.href) {
className = `${className} ${props.activeClassName}`;
}
return (
<Link {...props}>{React.cloneElement(child, { className })}</Link>
);
}
export default withRouter(ActiveLink);
Here's my version of ActiveLink
for Next.js + TypeScript:
import Link, { LinkProps } from "next/link";
import { NextRouter, useRouter } from "next/router";
import { FC, PropsWithChildren } from "react";
type ActiveLinkProps = LinkProps & {
activeClassName: string;
className: string;
};
export const ActiveLink: FC<ActiveLinkProps> = ({ children, ...props }: PropsWithChildren<ActiveLinkProps>) => {
const router: NextRouter = useRouter();
const className: string =
props.className + ((router.pathname === props.href && props.activeClassName) ? " " + props.activeClassName : "");
return (
<Link {...props}>
<a className={className}>{children}</a>
</Link>
);
};
Example of usage:
<ActiveLink href="/" className="menulink" activeClassName="active">
Home
</ActiveLink>
The difference is you no longer need <a>
tag inside.
Benefits:
- TypeScript-friendly
- You no longer need to find child element and its type with
Children.only
- You no longer need to do
React.cloneElement
- You won't forget to put
<a>
inside anymore.
Awesome, thanks a lot! Here is my more minimal version for when the className does not have to be set using a prop: