This content originally appeared on DEV Community and was authored by Elves Sousa
There is something that, at the moment I write these lines, still lacks in Next.js: a component <Link />
showing a different class while the page is being visited.
Why use the link if you can use normal anchors?
Before continuing, a small pause, to see why using <Link />
instead of an <a>
.
Basically, every time you use a normal anchor, the page makes a full refresh. The <Link />
component changes this behavior by loading only what changes on the screen, avoiding unnecessary rendering and making the experience faster and smoother. This is just for internal links; for the external ones, the anchor is enough.
React and Gatsby projects
In a React (CRA) project, this already comes by default with the React Router DOM library: just import a component <Link />
that comes with it, and add the activeClassName
attribute, informing a CSS class for the active state of that anchor.
import { Link } from "react-router-dom"
export function Nav() {
return (
<nav>
<Link to="/" activeClassName="active">
Home
</Link>
<Link to="/blog" activeClassName="active">
Blog
</Link>
<Link to="/about" activeClassName="active">
About
</Link>
</nav>
)
}
In Gatsby, another framework for creating static pages in React, the same can be achieved through the Gatsby library.
import { Link } from "gatsby"
export function Nav() {
return (
<nav>
<Link to="/" activeClassName="active">
Home
</Link>
<Link to="/blog" activeClassName="active">
Blog
</Link>
<Link to="/about" activeClassName="active">
About
</Link>
</nav>
)
}
However, in Next.js, for some reason I don't know yet, the implementation of the <Link />
component is quite different: a child element is required and there are no to
and activeClassName
properties.
import Link from "next/link"
export function Nav() {
return (
<nav>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/blog">
<a>Blog</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
</nav>
)
}
It is a good implementation, meets multiple needs, but still lacks support for a class for the active state, as seen in previous examples.
How to bring activeClassName support to Next.js
Let's now create the <ActiveLink />
: a component which will have the active class support. Here, the code is in typescript, but if your project uses JavaScript, the code works as well: just remove the typing. The component has only the required code for this feature to work.
First, we create the basic structure:
import { useRouter } from "next/router"
import Link from "next/link"
export function ActiveLink() {
const { asPath } = useRouter()
return <Link>...</Link>
}
The "hook" function useRouter
is imported from Next.js, so that our component has information for the current route. This hook has the asPath
property, which informs the current path of the page.
After this, let's create the properties of our component:
import { ReactElement } from "react"
import { useRouter } from "next/router"
import Link, { LinkProps } from "next/link"
type ActiveLinkProps = {
children: ReactElement
activeClassName: string
}
export function ActiveLink({ children, activeClassName }: ActiveLinkProps) {
const { asPath } = useRouter()
return <Link>{children}</Link>
}
Here, I use the ActiveLinkProps
type to inform the properties that the component will accept:
-
children: It is a
ReactElement
type, that is, accepts a single React element as a parameter. If aReactNode
orJSX.Element
type is used, it works as well, but as we will only have one element as child, is better toReactElement
. - activeClassName: With the 'string' type, as a simple text is enough to enter the name of a valid CSS class.
The problem is that at this time, the component doesn't have access to the properties of a normal <Link />
. To do this, you need to extend the ActiveLinkProps
type. Without these properties, the component will not work as a real replacement to the Next.js default link. Thus, it is necessary to import the Linkprops
definition that comes with next/link
:
import Link, { LinkProps } from "next/link"
After this, we make ActiveLinkProps
aware of LinkProps
type properties.
...
type ActiveLinkProps = LinkProps & {
children: ReactElement
activeClassName: string
}
...
Inside the component, an argument is then added to the function with the spread operator1, so that all the native properties of the Next.js link can be accessed and passed on to the returned component in the function.
import { ReactElement } from "react"
import { useRouter } from "next/router"
import Link, { LinkProps } from "next/link"
type ActiveLinkProps = LinkProps & {
children: ReactElement
activeClassName: string
}
export function ActiveLink({
children,
activeClassName,
...rest
}: ActiveLinkProps) {
const { asPath } = useRouter()
// The "...rest" represents all properties coming from LinkProps
return <Link {...rest}>...</Link>
}
Now just make a conditional that verifies if the current route is the same as the "href" of the component.
const className = asPath === rest.href ? activeClassName : ""
If true, the class informed in activeClassName
will be used.
Applying className in children components
Next.js' default implementation of <Link />
doesn't accept a className
property. This should be passed on to a child element, otherwise it will not work:
<Link href="/">
<a className="meuLink">Home</a>
</Link>
Therefore, to pass the property the correct way, we need to use the React.cloneElement()
2 method to clone the child element, and passing className
to it.
The final code will look like this:
import { cloneElement, ReactElement } from "react"
import { useRouter } from "next/router"
import Link, { LinkProps } from "next/link"
type ActiveLinkProps = LinkProps & {
children: ReactElement
activeClassName: string
}
export function ActiveLink({
children,
activeClassName,
...rest
}: ActiveLinkProps) {
const { asPath } = useRouter()
const className = asPath === rest.href ? activeClassName : ""
return <Link {...rest}>{cloneElement(children, { className })}</Link>
}
That's all folks!
Links
If this article helped you in some way, consider donating. This will help me to create more content like this!
-
React.cloneElement: See more at React docs ↩
This content originally appeared on DEV Community and was authored by Elves Sousa
Elves Sousa | Sciencx (2021-10-01T20:52:19+00:00) Creating a link with an active state in Next.js. Retrieved from https://www.scien.cx/2021/10/01/creating-a-link-with-an-active-state-in-next-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.