Active Class Name

🔗

The folder for this example is here

What this does

This component allows you to specify classes that you only want to be active when you are on the page it links to. A common usecase for this is in a navbar, you want the current page to be bold.

Creating the basic component

First we will create a component to wrap the Link tag in Next.js

import Link from "next/link"
const ActiveLink = (props) =>{
return(
<Link {...props} />
)
}
export default ActiveLink

Defining the props we need

This will forward all the props of the component to the Link component, making it act just like a Link component

Next up we want to add a required prop of activeClassName which is the classes we want to be applied when active

To do this we need the package PropTypes from npm, so run

npm install prop-types

or

yarn add prop-types

depending on which you are using.

Now we can import it at the top of our component and specify it at the bottom

import Link from "next/link"
import PropTypes from "prop-types"
const ActiveLink = (props) =>{
return(
<Link {...props} />
)
}
ActiveLink.propTypes = {
activeClassName: PropTypes.string.isRequired,
}
export default ActiveLink

As you can see from the type, activeClassName should be a string and is required

Getting the activeClassName in our component

Next we need to use this in our component, so we can modify the 3rd line defining the ActiveLink to the following

const ActiveLink = ({activeClassName, ...props}) => {

This will take activeClassName out of the props and provide it to the component, and provide the rest as props

Finding which page we are on

To find which page we are on, we will use the Router in Next.js, this will already be installed in your project, and you can import by adding

import {useRouter} from "next/router"

at the top of your code

You can see the object returned from useRouter here, what we are after is asPath as this will give us the path of the page you are on.

So inside the ActiveLink component we can write

const {asPath} = useRouter()

and the variable asPath will be the path of the page we are on

Handling the child component

Inside the Link component, there should be an a tag, and this is where styles are usually applied.

To find the details of this, we need to change our definition of the component to also take out the children prop, to make it like

const ActiveLink = ({ children, activeClassName, ...props }) => {

If there is more than one Link component, we want an error to be thrown as the structure should be an a tag inside the Link component. To verify this, we want to use Children.only from React

At the top of the code, import this like

import {Children} from "react"

Then we can add assign this to a variable, and will just get the child if there is just one, while we do this, we can also extract the className to manipulate

const child = Children.only(children)
const childClassName = child.props.className || ''

The definition of the childClassName has the or statement so that if the className isn't defined on the child, it will be an empty string instead

Merging the classNames

Now we can generate the final className we want, and to do this we will use the following ternary statement

const className =
asPath === props.href || asPath === props.as
? `${childClassName} ${activeClassName}`.trim()
: childClassName

This checks if the path is what is defined in either the href or as prop, the two options for the path specified in the Link component. If it is then it will set the variable to the concatenation of the two original className with the active className. .trim() here removes the spaces from each side of the final string.

Otherwise the className will be left as the original

Constructing the new component

Finally we can create the new component to be returned

In preparation we need to import React itself to clone an element, so modify the import of Children from React to be

import React, { Children } from 'react'

Now we can return the component

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

This will give a Link component, with all the props passed to the component apart from the ones separated out. Then React will clone the child element and modify the className to be the new one we have calculated, provided it exists, otherwise it won't set it.

Summary

The full code for the component is

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