I'm trying to build a menu bar where the user can hover over the options and the corresponding sub-menu shows.
I have implemented this behaviour using OnMouseEnter
and OnMouseLeave
. Everything works ok if I enter an item and then exit it without entering another but the problem arises when I move from one item to another directly. It seems that OnMouseLeave
is not fully executed before calling OnMouseEnter
on the new Item and this leads to the state not being correctly updated (this is a guess though).
Here is the code:
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import { BsChevronDown } from 'react-icons/bs';
import IMenuItemData from './Menu/IMenuItemData';
import NavigationMenu from './Menu/NavigationMenu';
function NavigationBar({menuData}:{menuData:IMenuItemData[]}) {
const [hoverStates, setHovering] = useState<any>({});
function _onMouseEnter(id:string|undefined)
{
console.log("OnMouseEnter id -> "+id);
console.log(hoverStates);
if(id)
{
let newState:any = {...hoverStates};
newState[id] = true;
setHovering(newState);
}
}
function _onMouseLeave(id:string|undefined)
{
console.log("OnMouseLeave id -> "+id);
console.log(hoverStates);
if(id)
{
let newState:any = {...hoverStates};
newState[id] = false;
setHovering(newState);
}
}
useEffect(()=>{
let states: any = {};
menuData.forEach( (el:IMenuItemData) =>{
if(el.id) states[el.id] = false;
});
setHovering(states);
},[]);
return (
<nav className="flex">
{menuData.map((e,index) => {
return<div key={index}>
<a href="#" onMouseEnter={()=>_onMouseEnter(e.id)} onMouseLeave={()=>_onMouseLeave(e.id)} className="flex p-2 items-center bg-red-400">
{e.text}
<BsChevronDown className="ml-2"></BsChevronDown>
</a>
{e.children && e.id ? <NavigationMenu data={e.children} visible={hoverStates[e.id]}></NavigationMenu> : null}
</div>
})}
</nav>
)
}
export default NavigationBar;
export default interface IMenuItemData
{
text: string,
children? : IMenuItemData[] ,
id?: string
}
This just iterates through IMenuItemData
objects and add them to the menu and adds a key to the state to track the hovering for every menu item.
This is the output when just entering an element and exiting without entering a new one:
OnMouseEnter id -> menu-item-store NavigationBar.tsx:15
Object { "menu-item-store": false, "menu-item-about": false, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:16
OnMouseLeave id -> menu-item-store NavigationBar.tsx:27
Object { "menu-item-store": true, "menu-item-about": false, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:28
And this the output that is logged when I leave a menu option but enters another immediately:
OnMouseEnter id -> menu-item-store NavigationBar.tsx:15
Object { "menu-item-store": false, "menu-item-about": false, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:16
OnMouseLeave id -> menu-item-store NavigationBar.tsx:27
Object { "menu-item-store": true, "menu-item-about": false, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:28
OnMouseEnter id -> menu-item-about NavigationBar.tsx:15
Object { "menu-item-store": true, "menu-item-about": false, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:16 <--- This should show 'menu-item-store': false
OnMouseLeave id -> menu-item-about NavigationBar.tsx:27
Object { "menu-item-store": true, "menu-item-about": true, "menu-item-community": true, "menu-item-support": false }
NavigationBar.tsx:28