I am new to React, and basically i want to build a megamenu from scratch. To do this i have event listeners for mouseenter
and mouseleave
events as well as the useState
hook from React.
import { useState, useEffect } from "react";
import { ArrowSmRightIcon } from "@heroicons/react/solid";
export default function Navbar({ props }) {
const { links } = props;
const [className, setclassName] = useState("bg-white/15 backdrop-blur-lg");
const [panelOpen, setpanelOpen] = useState(false);
const [usingPanel, setusingPanel] = useState(false);
const scrollHandler = (event) => {
if (window.scrollY < 30) {
setclassName("bg-white/15 backdrop-blur-lg");
} else {
setclassName("bg-white shadow-md");
}
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
}, []);
const linkHoverHandler = (e) => {
setpanelOpen(true);
};
const linkExitHandler = (e) => {
setTimeout(() => {
if (!usingPanel) {
setpanelOpen(false);
}
}, 500);
};
const panelHoverHandler = (e) => {
setusingPanel(true);
};
const panelExitHandler = (e) => {
setusingPanel(false);
setpanelOpen(false);
};
return (
<div className={`w-full ${className} transition-all sticky top-0`}>
<div className="container mx-auto relative">
<div className="flex items-center p-6 gap-6">
<img
className="h-10 flex-none"
src="thumbnail_image001.png"
alt="Digital Unity Logo"
/>
<ul className="flex flex-1">
{links.map((link, index) => (
<li
key={index}
className="ml-5 hover:cursor-pointer"
onMouseEnter={linkHoverHandler}
onMouseLeave={linkExitHandler}
>
{link.label}
</li>
))}
</ul>
</div>
{panelOpen && (
<div
onMouseEnter={panelHoverHandler}
onMouseLeave={panelExitHandler}
className="absolute top-[6rem] left-1/2 bg-white w-full -translate-x-1/2 flex items-center rounded-lg"
>
<div className="w-1/2 p-5 rounded-l-lg bg-gray-100 border-solid border-r-2 border-r-gray-200">
<div className="flex">
<div className="flex-1">
<h5 className="text-lg text-sky-800 mb-5">Οι υπηρεσίες</h5>
<ul>
{["Η μια", "Η δυο", "Η τρια"].map((el, i) => (
<li
key={i}
className="mb-2 hover:text-sky-600 hover:cursor-pointer transition-all"
>
{el}
</li>
))}
</ul>
</div>
<div className="flex-1">
<h5 className="text-lg text-sky-800 mb-5">Οι alles</h5>
<ul>
{["Η μια", "Η δυο", "Η τρια"].map((el, i) => (
<li
key={i}
className="mb-2 hover:text-sky-600 hover:cursor-pointer transition-all"
>
{el}
</li>
))}
</ul>
</div>
</div>
<div>
<p className="hover:text-sky-600 hover:cursor-pointer my-2 flex items-center">
Ανακάλυψε τα έργα
<ArrowSmRightIcon className="h-5 w-5 text-sky-500" />
</p>
<p className="hover:text-sky-600 hover:cursor-pointer my-2 flex items-center">
Δες τα testemonials
<ArrowSmRightIcon className="h-5 w-5 text-sky-500" />
</p>
</div>
</div>
<div className="w-1/2 rounded-lg p-5">Diff Section</div>
</div>
)}
</div>
</div>
);
}
Ignore all the greek in there
I get this very unexpted behaviour where setusingPanel
doesn't mutate properly on the first time but only on the second.
Even when i log out the usingPanel variable it is only changed correctly the second time around. Am I using the hooks incorrectly?
It's because
const linkExitHandler = (e) => {
setTimeout(() => {
if (!usingPanel) {
setpanelOpen(false);
}
}, 500);
};
creates a closure over the current value of usingPanel
and does not contain the latest state.
You could make it a ref
(see useRef() ). But I find it that construct with setpanelOpen
but not if usingPanel
too complicated; it soon gets unmanageable how these two interfere with each other.
You can use clearTimeout()
and a ref to store the timeout identifier:
const [panelOpen, setpanelOpen] = useState(false);
const timeoutRef = useRef();
const showPanel = () => {
clearTimeout(timeoutRef.current);
setpanelOpen(true);
}
const hidePanel = () => {
clearTimeout(timeoutRef.current);
setTimeout(setpanelOpen, 500, true);
}
// and use
onMouseEnter={showPanel}
onMouseLeave={hidePanel}
//on both links and panel
const _takeLatest = (current, changes) => current && changes.ts < current.ts? current: changes; const [{ panelOpen = false }, dispatch] = useReducer(_takeLatest); const showPanel = () => { dispatch({ panelOpen: true, ts: precision.now() }); } const hidePanel = () => { setTimeout(dispatch, 500, { panelOpen: false, ts: precision.now() }); }
Another thing, of topic:
useEffect(() => { const scrollHandler = (event) => { if (window.scrollY < 30) { setclassName("bg-white/15 backdrop-blur-lg"); } else { setclassName("bg-white shadow-md"); } }; window.addEventListener("scroll", scrollHandler); return () => { window.removeEventListener("scroll", scrollHandler); } }, []);
scrollHandler
inside of useEffect
. We don't need it outside and that way you avoid confusion as to which function from which render is used here; the same problem you currently have with usingPanel
;) 2. Clean up after yourself: remove the event handlers you add.
This seems like a pretty global component that will most likely exist as long as the page is loaded, but with smaller components you prevent them from being garbage collected, because that event handler still has a reference to them.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.