I apologise in advance if this is a silly question. Although I have managed to get it to work, I would like to get a deeper understanding.
I am building a custom hamburger menu in react which closes whenever you click anywhere outside the unordered list or the hamburger icon itself.
I have seen answers here Detect click outside React component And I have followed it but I couldn't understand why it wasn't working.
Firstly when it was just the hamburger icon and no click outside the menu to close option, it worked perfectly. Then when I used the useRef hook to get a reference to the unordered list in order to only close the menu when the list is not clicked, it worked perfectly except for when I clicked the actual hamburger icon.
After a lot of amateur debugging I finally realised what was happening. First when I opened the menu the state showMenu changed to true, Then when I clicked the hamburger icon to close, The parent wrapper element was firing first instead of the hamburger menu which is strange as during the bubbling phase I would expect the inner most element to fire first.
So the parent element would close the menu changing the state, causing the components to re-render. Then when the event would reach the actual icon the handleClick would once again toggle the state to true giving the impression that the hamburger click isn't working.
I managed to fix this by using event.stopPropogation() on the parent element. But this seems very strange because I would not expect the parent element's click to fire first especially when Im using bubbling phase. The only thing I can think of is because it is a native dom addeventlistener event it is firing first before the synthetic event.
Below is the code for the Mobile navigation which has the hamburger The header component renders the normal Nav or the MobileNav based on screen width. I haven't put code for the higher order components to make it easier to go through, but I can provide all the code if needed:
//MobileNav.js
export default function MobileNav() {
const [showMenu, setShowMenu] = useState(false);
const ulRef = useRef();
console.log('State when Mobilenav renders: ', showMenu);
useEffect(() => {
let handleMenuClick = (event) => {
console.log('App Clicked!');
if(ulRef.current && !ulRef.current.contains(event.target)){
setShowMenu(false);
event.stopPropagation();
}
}
document.querySelector('#App').addEventListener('click', handleMenuClick);
return () => {
document.querySelector('#App').removeEventListener('click', handleMenuClick);
}
}, [])
return (
<StyledMobileNav>
<PersonOutlineIcon />
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{
(showMenu) &&
<ul ref={ulRef} style={{
backgroundColor: 'green',
opacity: '0.7',
position: 'absolute',
top: 0,
right: 0,
padding: '4em 1em 1em 1em',
}}
>
<MenuList/>
</ul>
}
</StyledMobileNav>
)
}
//MenuIcon.js
/**
* By putting the normal span instead of the MenuLine component after > worked in order to hover all div's
*/
const MenuWrap = styled.div`
width: 28px;
position: relative;
transform: ${(props) => props.showMenu ? `rotate(-180deg)` : `none` };
transition: transform 0.2s ease;
z-index: 2;
&:hover > div{
background-color: white;
}
`;
const MenuLine = styled.div`
width: 100%;
height: 2px;
position: relative;
transition: transform 0.2s ease;
background-color: ${(props) => props.showMenu ? 'white' : mainBlue};
&:hover {
background-color: white;
}
`;
const TopLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `top: 9px; transform: rotate(45deg);`;
}
return style;
}}
`;
const MidLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `opacity: 0;`;
}
return style;
}}
`;
const BottomLine = styled(MenuLine)`
${props => {
if(props.showMenu){
return `bottom: 9px; transform: rotate(-45deg);`;
}
}}
`;
export default function MenuIcon({showMenu, setShowMenu}) {
const handleMenuClick = (event) => {
console.log('Menu Clicked!');
console.log('State before change Icon: ', showMenu);
setShowMenu(!showMenu);
}
return (
<MenuWrap onClick={handleMenuClick} showMenu={showMenu}>
<TopLine onClick={handleMenuClick} showMenu={showMenu}></TopLine>
<MidLine onClick={handleMenuClick} showMenu={showMenu}></MidLine>
<BottomLine onClick={handleMenuClick} showMenu={showMenu}></BottomLine>
</MenuWrap>
)
}
Reading this article https://dev.to/eladtzemach/event-capturing-and-bubbling-in-react-2ffg basically it states that events in react work basically the same way as DOM events
But for some reason event bubbling is not working properly See screenshots below which show how the state changes:
Can anyone explain why this happens or what is going wrong?