Issue
In my React JS website I have a div which acts as an underline in the navbar showing what ever route the user is at.
The div underline element moves over to a new route whenever it is clicked on by the user using css transitions.
The problem is whenever I resize the window the navbar resizes moving the clickable routes as well (which I want it to do). However the div underline element stays in the same spot so is now underlining a random part of the navbar.
My question is how do I constantly keep the div under the selected route even on resize. Here is my code below:
React:
import { useEffect, useState, useRef } from "react"
import { useLocation } from "react-router-dom"
export default function Underline() {
let pathname = useLocation().pathname
const [underlineStyle, setUnderlineStyle] = useState()
const width = useRef(window.innerWidth)
useEffect(() => {
function handleResize() {
width.current = window.innerWidth
}
window.addEventListener("resize", handleResize())
let underlinedLink = document.querySelector(`a[href="${pathname}"]`)
//as there are 2 anchor tags for home in nav
if (underlinedLink.getAttribute("id") !== null) { //both home anchor tags are only ones with id's in nav
underlinedLink = document.querySelector("#home")
}
setUnderlineStyle({
width: `${underlinedLink.offsetWidth}px`,
left: `${underlinedLink.getBoundingClientRect().left}px`,
})
return () => {
window.removeEventListener("resize", handleResize())
}
}, [pathname, width.current])
return <div id="underline" style={underlineStyle}></div>
}
CSS:
#underline {
margin: 0 0 5px 0;
height: 4px;
bottom: 0;
border: 0;
position: absolute;
transition: left 0.3s ease-in-out, width 0.3s ease-in-out;
background: linear-gradient(to bottom, #48a0e0 20%, #2b58a5);
box-shadow: 0 -14px 14px #48a0e0;
}
I have tried adding resize event listeners that call a function to move the underline which technically worked but it doesn't constantly stay with the route and only moves to the route when I am done resizing which would be a very strange user experience.
I am also aware I could put the underline div into an ancestor element with both the underline div and the route but that seems like an overly complicated way to do it and am curious if anyone knows any simpler ways to do it. Any suggestions or solutions would be much appreciated.
Solution
Create an updateUnderline
function inside the useEffect
. Call the function immediately when pathname
changes, and use it as the resize handler as well.
Note: if you're using a transition on the underline, remove it while resizing.
Working Example:
const { useState, useEffect } = React
function Underline({ pathname }) {
const [underlineStyle, setUnderlineStyle] = useState()
useEffect(() => {
function updateUnderline(evt) {
const underlinedLink = document.querySelector(`a[href="${pathname}"]`)
setUnderlineStyle({
width: `${underlinedLink.offsetWidth}px`,
left: `${underlinedLink.getBoundingClientRect().left}px`,
...!!evt && { transition: 'none' }
})
}
updateUnderline() // call whenever the useEffect is triggered
window.addEventListener("resize", updateUnderline) // use as event handler
return () => {
window.removeEventListener("resize", updateUnderline)
}
}, [pathname])
return (
<div id="underline" style={underlineStyle} />
)
}
function Links({ links }) {
const [pathname, setPathname] = useState(links[0])
return (
<div className="container">
<div>{
links.map(href => (
<a
key={href}
href={href}
onClick={() => setPathname(href)}>{href}
</a>
))}
</div>
<Underline pathname={pathname} />
</div>
)
}
ReactDOM
.createRoot(root)
.render(<Links links={['#aaaa', '#bbbb', '#cccc', '#dddd']} />)
.container {
text-align: center;
}
a {
display: inline-block;
margin: 0 1em;
text-decoration: none;
}
#underline {
position: absolute;
border-bottom: 2px solid red;
transition: left 0.3s;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>
Code with comments:
export default function Underline() {
const pathname = useLocation().pathname
const [underlineStyle, setUnderlineStyle] = useState()
useEffect(() => {
function updateUnderline(evt) {
let underlinedLink = document.querySelector(`a[href="${pathname}"]`)
//as there are 2 anchor tags for home in nav
if (underlinedLink.hasAttribute("id")) { //both home anchor tags are only ones with id's in nav
underlinedLink = document.querySelector("#home")
}
setUnderlineStyle({
width: `${underlinedLink.offsetWidth}px`,
left: `${underlinedLink.getBoundingClientRect().left}px`,
...!!evt && { transition: 'none' } // remove transition when resizing
})
}
updateUnderline() // call whenever the useEffect is triggered
window.addEventListener("resize", updateUnderline) // use as event handler
return () => {
window.removeEventListener("resize", updateUnderline)
}
}, [pathname])
return <div id="underline" style={underlineStyle}></div>
}
Answered By - Ori Drori
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.