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:
- I gotta DropdownMenu2 component, which
display
is being toggled byonMouseEnter/Leave
events. This component (my dropdown menu) holds inside<NavLink>
menu items. - I wanted the dropdown menu to disappear after clicking on menu item, so inside
<Navlink>
I createdonClick
event which triggershandleClick
. This functions sets aclick
variable - to a CSSclassName
withdisplay:none
.click
is then passed to<div>
that contains the Dropdown menu. - To toggle the dropdown menu
display
again on mouse hover, I had to get rid of theclick
class from thediv
. For that I createduseEffect
hook, withclick
dependency - so it fires every timeclick
state changes. And function inside this hook - changesclick
value, so it no longer represents the CSSdisplay:none
class. So after (2.) -div
containing dropdown menu hasdisplay:none
, disapears, anduseEffect
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"
onm ouseEnter={hoverOn}
onm ouseLeave={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
CodePudding user response:
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)
- Try and always get the latest state when you update the next state.
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. Thus this might solve your issue.
Let me know if this works. (Please accept the answer if it does)
CodePudding user response:
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)