I followed the documentation to create a dropdown menu using Material UI. However, none of the docs have a good example for handling multiple dropdowns in the same menu.
I got it mostly working - however, when I open a dropdown, they ALL open. I'm assuming this is because open = Boolean(anchorEl) opens the menu whenever an anchorEl is set. So how can I adjust this so it only opens the specific menu that is clicked?
const NavBarMainMenu = () => {
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const handleClick = event => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
return (
<>
<Box sx={{ flexGrow: 1, display: { xs: "none", lg: "flex" } }}>
{pages.map(page => {
return (
<>
<Button
key={page.title}
id={page.title + "-button"}
onClick={handleClick}
aria-controls={open ? page.title : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
>
{page.title}
</Button>
<Menu
anchorEl={anchorEl}
id={page.title}
open={open}
onClose={handleClose}
onClick={handleClose}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
>
{page.children.map(child => {
return <MenuItem key={child.title}>{child.title}</MenuItem>
})}
</Menu>
</>
)
})}
</Box>
</>
)
}
You can try something like this:
const NavBarMainMenu = () => {
const [anchorEls, setAnchorEls] = useState({}) // <-- Here use a object
const isOpen = (id) => Boolean(anchorEls[id]) // <-- Here you need a function to get if open is true
const handleClick = (event, id) => {
setAnchorEsl({ ...anchorEls, [id]: event.currentTarget }) // <-- Here you set anchor value using an id
}
const handleClose = (id) => {
setAnchorEls({ ...anchorEls, [id]: null }) // <-- Here you delete anchor value by ID
}
return (
<>
<Box sx={{ flexGrow: 1, display: { xs: "none", lg: "flex" } }}>
{pages.map(page => {
return (
<>
<Button
key={page.title}
id={page.title + "-button"}
onClick={(event) => handleClick(event, page.title)}
aria-controls={isOpen(page.title) ? page.title : undefined}
aria-haspopup="true"
aria-expanded={isOpen(page.title) ? "true" : undefined}
>
{page.title}
</Button>
<Menu
anchorEl={anchorEls[page.title]}
id={page.title}
open={isOpen(page.title)}
onClose={() => handleClose(page.title)}
onClick={() => handleClose(page.title)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
>
{page.children.map(child => {
return <MenuItem key={child.title}>{child.title}</MenuItem>
})}
</Menu>
</>
)
})}
</Box>
</>
)
}
Related
I have a menu. in some MenuItems must appear submenu while hovering.
onMouseOver is working correctly but when i leave mouse from menuitem it is not closing
here is my functions
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
if (anchorEl !== event.currentTarget) {
setAnchorEl(event.currentTarget);
}
};
const handleClose = () => {
setAnchorEl(null);
};
const navConf = NavigateConfig();
here is my Jsx
<MenuList sx={{
display: { xs: 'none', lg: 'block' }, display: 'flex'
}}>
{navConf.map((item) => (
<>
<MenuItem
// id="simple-menu"
onMouseOver={item.child ? (e) => handleClick(e) : () => { }}
aria-owns={anchorEl ? "simple-menu" : undefined}
aria-haspopup="true"
>
{item.title}
{anchorEl &&
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{ onMouseLeave: handleClose, 'aria-labelledby': 'simple-menu', }}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<div className='topDiv'></div>
{item.child?.map((e) => {
return <MenuItem>{e.title}</MenuItem>
})}
</Menu>}
</MenuItem>
</>
))}
<LanguagePopover />
</MenuList>
submenu is disappearing when i click outside of it
Do you just need to add an onMouseLeave?
<MenuItem
onMouseOver={item.child ? (e) => handleClick(e) : () => { }}
onMouseLeave={item.child ? (e) => handleClose() : () => { }}
onMouseOver runs every time your mouse moves and is overlapping the element, but it wont automatically undo what it does when the mouse leaves again.
I have an array of data and map it into Popover component. These components have MenuItem which contains some data. But they render only the last data, instead of the different data that exists. Example:
Wrong data, should be to 59 as shown below. The textfield and popover contain the same MenuItem components, only thing different is the popovers.
Code:
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
// New section
<Fab
className={classes.fabStyle}
size="small"
variant="outlined"
onClick={handlePopoverClick}
>
<ExpandMoreIcon />
</Fab>
<Popover
id={id}
open={open}
className={classes.popoverContainer}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
PaperProps={{ elevation: 1 }}
>
{action.children.map((child) => {
if (child.type === "link") {
console.log(child.to);
return (
<MenuItem>
<Link
style={{
textDecoration: "none",
color:
themes.default.palette
.text.primary,
}}
to={child.to}
>
{child.label}
{child.to}
</Link>
</MenuItem>
);
} else if (
child.type === "dialog"
) {
return (
<MenuItem
onClick={() =>
child.handleOpenDialog(row)
}
>
{child.label}
</MenuItem>
);
}
})}
</Popover>
I am following the react material ui doc here: https://mui.com/components/popover/, and I'm attempting to factor all the popover view and state logic into a higher order component. The one from the doc is here:
function BasicPopoverFromDoc(props) {
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
style={{color:'white'}}
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
I lifted all the state logic into a hoc as follows, taking <Child/> as a param:
function WithPopOver(props){
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const { Child } = props;
console.log('with Popover: ', open)
return (
<div>
<Child
{...props}
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
/>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
but when i try to use it this way:
function BasicPopover(props){
return (
<WithPopOver
{...props}
Child={() => <Typography style={{color:'white'}}> wrapped in hoc </Typography>}
/>
)
}
It seems like the popover does not display. It refuses to display even when I change open to true by force. What am i missing here?
You haven't passed Child props to it's children, so onMouseEnter and onMouseLeave never fires, try on this:
<WithPopOver
{...props}
Child={(props) => <Typography style={{color:'white'}} {...props}> wrapped in hoc </Typography>}
/>
I'm using material-ui popover component wrapping a formik form. it's obvious every time it is closed, the form re renders and all values get cleaned.
I thought about storing values in parent, but it's more complicated and I'm using dynamic component, then it's not a solution here.
I am looking for a way to get a copy of children and just show it inside popover.
Here is my code:
export default function ButtonPopover({ icon: Icon, children }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = (event) => {
setAnchorEl(event.currentTarget);
};
const close = () => {
setAnchorEl(null);
};
return (
<>
<IconButton onClick={open}>
<Icon />
</IconButton>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={close}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
// Here is what I need to be saved.
{children}
</Popover>
</>
);
}
I'm not sure what you're trying to do but this is how you render conditionally:
export default function ButtonPopover({ icon: Icon, children }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = (event) => {
setAnchorEl(event.currentTarget);
};
const close = () => {
setAnchorEl(null);
};
const renderChildren = () => {
if (myCondition == true) {
return (
<>
<Text>render when condition equals true</Text>
</>
)
} else {
return (
<>
<Text>render when condition doesn't equal true</Text>
</>
)
}
}
return (
<>
<IconButton onClick={open}>
<Icon />
</IconButton>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={close}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
{renderChildren()}
</Popover>
</>
);
}
I have the below menu:
So, ideally you'd click on those 3 dots which would open up another box next to it, I decided to use a popover(https://material-ui.com/components/popover/).. The problem is that when you click the 3 dots nothing happens. I assume this is because onClick function returns a functional popover component but that doesn't get displayed. I put in debuggers and alerts inside the functional component no hit at all.
This is those 3 dots
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
This is moreClick function
moreClick = (e, officeAccount) => {
return (
<AccountFavoritesPopover element={e} officeAccount={officeAccount} />
);
};
This is the whole popover
import React from "react";
import Popover from "#material-ui/core/Popover";
export default function AccountFavoritesPopover(element, officeAccount) {
const anchorEl = element;
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
//onClose={alert}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{officeAccount}</div>
</Popover>
</div>
);
}
Does popover have to be inside the main file? Currently the 3 dots is in the main file and AccountFavoritesPopover is a whole separate file.
I attempted to put "AccountFavoritesPopover" code inside the main file but I cannot utilize useState in the main file because it's a class. Also, I cannot convert it to actual state and use setState because once setState kicks in, the menu will disappear...
Edit:
Below is the Menu
<Creatable
id="Select"
menuIsOpen={this.state.Focused}
components={{
Option: this.Option
}}
/>
Below is the Options inside menu
<div style={{ position: "relative" }}>
<components.Option {...props} />
<div id="MoreBox">
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
</div>
</div>
Try this, this should work(Not tested)
Main.js
export default class Main extends Component {
constructor(props) {
this.state = {
selectedIndex: 0,
selectedId: 0,
anchorEl: null
};
}
moreClick = (anchorEl, selectedId, selectedIndex) => {
this.setState({
selectedId,
selectedIndex,
anchorEl,
open: true,
});
}
handleClose = () => {
this.setState({
open: false
});
}
render() {
const menu = [
{
id: 1,
text: '002',
more: 'more 003'
},
{
id: 2,
text: '003',
more: 'more 003'
},
{
id: 3,
text: '004',
more: 'more 003'
}
]
const menuDom = menu.map((m, index) => {
return (
<IconButton
key={m.id}
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e.currentTarget, index, m.id)}>
{m.text}
</IconButton>
)
})
const more = (<More>{menu[this.state.selectedIndex].text}</More>)
return (
<div>
{menuDom}
<AccountFavoritesPopover open={this.state.open} anchorEl={this.state.anchorEl} onClose={this.handleClose}>
{more}
</AccountFavoritesPopover>
</div>
)
}
}
AccountFavoritesPopover.js
export default function AccountFavoritesPopover(open, anchorEl, onClose) {
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{this.props.children}</div>
</Popover>
</div>
);
}