Reputation: 123
I wanted to create a dropdown menu, which shows itself and hides on hovering, and disappears after clicking its item. I thought I found a way to do it - but it works only sometimes. (Or maybe it doesn't work - but sometimes it does.) Details below:
display
is being toggled by onMouseEnter/Leave
events. This component (my dropdown menu) holds inside <NavLink>
menu items.<Navlink>
I created onClick
event which triggers handleClick
. This functions sets a click
variable - to a CSS className
with display:none
. click
is then passed to <div>
that contains the Dropdown menu.display
again on mouse hover, I had to get rid of the click
class from the div
. For that I created useEffect
hook, with click
dependency - so it fires every time click
state changes. And function inside this hook - changes click
value, so it no longer represents the CSS display:none
class. So after (2.) - div
containing dropdown menu has display:none
, disapears, and useEffect
erases that - making it hover ready.problem:
this works only sometimes - sometimes useEffect
is triggered so fast after onClick
, that the dropdown menu doesn't even drop. ( click
changes so fast that div
container gets the "erasing" class immediately after display:none
class )
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
import useHoverButton from "./sub-components/useHoverButton";
const NaviMainButtonDrop2 = () => {
const { disp, hoverOn, hoverOff } = useHoverButton();
return (
<li
className={`nav-main__button dropdown-us`}
>
<a
className="hover-pointer"
onMouseEnter={hoverOn}
onMouseLeave={hoverOff}
>
title
</a>
{ disp && <DropdownMenu2 /> }
</li>
)
}
export default NaviMainButtonDrop2
useHoverButton (custom hook for NaviMainButtonDrop2)
import { useState } from "react";
const useHoverButton = () => {
const [disp, setDisp] = useState(false);
const hoverOn = () => setDisp(true)
const hoverOff = () => setDisp(false)
return { disp, hoverOn, hoverOff }
}
export default useHoverButton
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { useEffect } from "react";
import useAddClass from "./sub-components/useAddClass";
const DropdownMenu2 = () => {
const { click, setClick, handleClick } = useAddClass("hide-menu");
useEffect(() => {
console.log("[usEffect]")
setClick("");
}, [click]);
return (
<div className={`dropdown-holder-us ${click}`}>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
onClick={handleClick}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
useAddClass (custom hook for DropdownMenu2)
import { useState } from "react"
const useAddClass = (className) => {
const [click, setClick] = useState("");
const handleClick = () => setClick(className);
return { click , handleClick }
}
export default useAddClass
Upvotes: 0
Views: 674
Reputation: 160
I think the issue here is that you are not able to get the latest state whenever you update the next state that is why it works sometimes and sometimes it doesn't.
According to me there could be 2 solutions to this, either use a setTimeout
or get the latest state when setting the state.
setTimeout
solution- useEffect(() => {
setTimeout(() => {
setClick("")
},2000)
useEffect(() => {
console.log("[usEffect]")
setClick((clickLatest) => "");
}, [click]);
and
const handleClick = () => setClick((clickLatest) => className);
This callback will help the useState
wait for the latest state and then update the state further.
Upvotes: 1
Reputation: 123
I think I just found a simple solution to this. I don't understand why useEffect
seems to work in a random timing, but using setTimeOut
inside it, and delaying the execution of setClick
- seems to do the job.
useEffect(() => {
setTimeout(() => {
setClick("")
},2000)
Upvotes: 0