Open menu on mouseover and Close menu on mouseleave in react
Asked Answered
M

2

9

I just started plating around with react. I am currently working on my navBar using material-ui and react. When I hover over the menu, the drop-down appears. But in order to close the drop-down, I have to click on the outside of the drop-down. I want to be able to close the dropdown when I hover out of the drop-down or move to the different menu option (in which case a different drop-down should appear). Something like this one: https://www.palantir.com/

I looked around but I didn't find the solution. This was the closest I got: Material-ui: open menu by event hover

I tried using the same technique and added this to my code but to no avail. Any suggestions? Thanks!

Edits: I recreated my problem here: https://react-xmaiyw.stackblitz.io The problem can be seen when clicked on 'Why us'.

 handleClick = (event) => {
 event.preventDefault();

   this.setState({
    open: true,
    anchorEl: event.currentTarget,
   });
 };

handleRequestClose = () => {
  this.setState({
   open: false,
  });
};

render() {
return (
  <FlatButton
  onClick={this.handleClick}
  onMouseOver={this.handleClick}
  onMouseLeave={this.handleRequestClose} //When I add this line of 
     //code, it keeps flickering very fast almost as if drop-down 
     //doesn't open
  label="Why Us?"
/>
)}
Marzi answered 28/12, 2017 at 20:24 Comment(2)
I don't know if this is the answer you are looking for, since you seem to have already written a good deal of javascript, but this can be done quite simply without javascript using the CSS pseudo element hover. W3Schools has an excellent tutorial on how to implement this in a dropdown menu setting here If this isn't the effect you were looking for, please include your markup so that we can see more fully what you're going for.Trysail
I am trying to make it work with js as compared to CSS. I have edited my original question and added a working link to my problem. Thanks :)Marzi
M
18

The flickering is caused by the opening of the menu underneath your mouse. When the menu opens, the mouse is no longer over the button, so it prompts a mouseleave event, closing the menu, so that your mouse is now above the button again, prompting a mouseenter event, which opens the menu...and so on and so forth.

You can accomplish what you'd like with some additional logic to track where the mouse is, and a timeout to ensure that the user has time to transition the mouse between the button and the menu.

import React from 'react';
import Button from 'material-ui/Button';
import Menu, { MenuItem } from 'material-ui/Menu';

const timeoutLength = 300;

class SimpleMenu extends React.Component {
  state = {
    anchorEl: null,

    // Keep track of whether the mouse is over the button or menu
    mouseOverButton: false,
    mouseOverMenu: false,
  };

  handleClick = event => {
    this.setState({ open: true, anchorEl: event.currentTarget });
  };

  handleClose = () => {
    this.setState({ mouseOverButton: false, mouseOverMenu: false });
  };

  enterButton = () => {
    this.setState({ mouseOverButton: true });
  }

  leaveButton = () => {
    // Set a timeout so that the menu doesn't close before the user has time to
    // move their mouse over it
    setTimeout(() => {
      this.setState({ mouseOverButton: false });
    }, timeoutLength);
  }

  enterMenu = () => {
    this.setState({ mouseOverMenu: true });
  }

  leaveMenu = () => {
     setTimeout(() => {
      this.setState({ mouseOverMenu: false });
     }, timeoutLength);
  }

  render() {
    // Calculate open state based on mouse location
    const open = this.state.mouseOverButton || this.state.mouseOverMenu;

    return (
      <div>
        <Button
          aria-owns={this.state.open ? 'simple-menu' : null}
          aria-haspopup="true"
          onClick={this.handleClick}
          onMouseEnter={this.enterButton}
          onMouseLeave={this.leaveButton}
        >
          Open Menu
        </Button>
        <Menu
          id="simple-menu"
          anchorEl={this.state.anchorEl}
          open={open}
          onClose={this.handleClose}
          MenuListProps={{
            onMouseEnter: this.enterMenu,
            onMouseLeave: this.leaveMenu,
          }}

        >
          <MenuItem onClick={this.handleClose}>Profile</MenuItem>
          <MenuItem onClick={this.handleClose}>My account</MenuItem>
          <MenuItem onClick={this.handleClose}>Logout</MenuItem>
        </Menu>
      </div>
    );
  }
}

export default SimpleMenu;

I used the MenuListProps to set the mouseEnter and mouseLeave events directly on the MenuList itself because the Menu component includes a bunch of invisible (disply: none) transition elements that have weird effects on mouse events. The MenuList is the element that's actually displayed so it makes sense to set the mouse events directly on it.

You'll probably need to play around with the timeoutLength and transitions to get everything looking smooth.

Mcleod answered 31/12, 2017 at 20:35 Comment(0)
L
2

I faced same problems. I solved the issues like this. I gaved LeaveMenu event to total component and menu component seperately, after it, it worked perfectly

import React from 'react';
import {
  Menu,
  MenuItem as MuiMenuItem,
  Avatar,
  Divider,
  Typography,
  Switch,
  Fade,
} from '@mui/material';
import { useHistory } from 'react-router-dom';
import { styled } from '@mui/styles';
import { DarkMode as DarkModeIcon } from '@mui/icons-material';

/********************  Styled Components  ********************/
const UserAvatarButton = styled('div')(({ active, theme }) => ({
  height: 72,
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  padding: '0px 20px',
  cursor: 'pointer',
  borderBottom: active ? `3px solid ${theme.palette.primary.main}` : 'none',
  borderRadius: 0,
}));

const ProfileMenuNavigation = styled(Menu)(() => ({
  '& .MuiList-root': {
    paddingTop: 0,
    paddingBottom: 0,
    minWidth: 220,
    maxWidth: 350,
  },
}));

const MenuItem = styled(MuiMenuItem)(({ theme }) => ({
  padding: 16,
  width: '100%',
  '&:hover': {
    backgroundColor: theme.palette.background.main,
    boxShadow: '5px 0px 5px 0px #888888',
    transition: 'box-shadow 0.3s ease-in-out',
  },
}));

const ProfileMenuText = styled(Typography)(() => ({
  fontFamily: 'Poppins',
  marginLeft: 16,
  marginRight: 16,
  fontSize: 16,
  fontWeight: 600,
}));

/********************  Main Component  ********************/
const ProfileMenu = ({ menus, active }) => {
  const history = useHistory();

  const [anchorEl, setAnchorEl] = React.useState(null);
  const open = Boolean(anchorEl);

  const handleClick = (event) => {
    if (anchorEl) {
      setAnchorEl(null);
    } else {
      setAnchorEl(event.currentTarget);
    }
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  const goPath = (path) => {
    setAnchorEl(null);
    history.push(path);
  };

  const leaveMenu = () => {
    setTimeout(() => {
      setAnchorEl(null);
    }, 300);
  };

  return (
    <div onMouseLeave={leaveMenu}>
      <UserAvatarButton
        id="account-button"
        active={active}
        aria-controls={open ? 'account-menu' : undefined}
        aria-haspopup="true"
        aria-expanded={open ? 'true' : undefined}
        onClick={handleClick}
        onMouseOver={(event) => setAnchorEl(event.currentTarget)}
      >
        <Avatar
          sx={{
            width: 38,
            height: 38,
          }}
          alt="Avatar"
          src="https://i.pravatar.cc/300"
        />
      </UserAvatarButton>
      <ProfileMenuNavigation
        id="account-menu"
        anchorEl={anchorEl}
        open={open}
        onClose={handleClose}
        MenuListProps={{
          'aria-labelledby': 'account-button',
          onMouseLeave: leaveMenu,
        }}
        anchorOrigin={{
          vertical: 'bottom',
          horizontal: 'right',
        }}
        transformOrigin={{
          vertical: 'top',
          horizontal: 'right',
        }}
        TransitionComponent={Fade}
      >
        {menus.map((menu, index) => (
          <div key={index}>
            <MenuItem onClick={() => goPath(menu.path)}>
              {menu?.icon}
              <ProfileMenuText>{menu.text}</ProfileMenuText>
            </MenuItem>
            <Divider style={{ margin: 0 }} />
          </div>
        ))}
        <MenuItem onClick={() => {}}>
          <DarkModeIcon />
          <ProfileMenuText>Night Mode</ProfileMenuText>
          <div style={{ marginLeft: 16 }}>
            <Switch />
          </div>
        </MenuItem>
      </ProfileMenuNavigation>
    </div>
  );
};

export default ProfileMenu;
Lewis answered 11/1, 2022 at 13:55 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Discommodity

© 2022 - 2024 — McMap. All rights reserved.