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 ActiveLinkDefining 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-typesor
yarn add prop-typesdepending 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 ActiveLinkAs 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() : childClassNameThis 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