React JS / Material UI - Call method from imported Component - javascript

I'm trying to call the handleLoginOpen method from LoginModal.js, which I imported in File1.js.
File1 is actually a component nested in an AppBar, where i have an account IconButton that shows two MenuItem: Login or Register. If I press login I would like to show a Modal with the login form.
Here the two files:
File1.js:
import React from "react";
import { IconButton, Menu, MenuItem } from "#material-ui/core";
import AccountCircleIcon from "#material-ui/icons/AccountCircle";
import LoginModal from "../components/LoginModal";
export default function AccountNotRegistered() {
const [anchorEl, setAnchorEl] = React.useState(null);
const isMenuOpen = Boolean(anchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleProfileMenuClose = () => {
setAnchorEl(null);
};
const menuId = "account-menu";
const renderMenu = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
id={menuId}
keepMounted
transformOrigin={{ vertical: "top", horizontal: "right" }}
open={isMenuOpen}
onClose={handleProfileMenuClose}
>
<MenuItem onClick={handleLoginOpen}>Login</MenuItem> <------------------- HERE I CALL THE METHOD
<MenuItem onClick={handleProfileMenuClose}>Register</MenuItem>
</Menu>
);
return (
<div>
<IconButton color="inherit" onClick={handleProfileMenuOpen}>
<AccountCircleIcon />
</IconButton>
<LoginModal />
{renderMenu}
</div>
);
}
LoginModal.js:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Modal from "#material-ui/core/Modal";
import { Backdrop } from "#material-ui/core";
import Fade from "#material-ui/core/Fade";
const useStyles = makeStyles((theme) => ({
modal: {
display: "flex",
alignItems: "center",
justifyContent: "center",
},
paper: {
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
export default function LoginModal() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleLoginOpen = () => { <----------------------------------HERE IS THE METHOD
setOpen(true);
};
const handleLoginClose = () => {
setOpen(false);
};
return (
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleLoginClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">Transition modal</h2>
<p id="transition-modal-description">
react-transition-group animates me.
</p>
</div>
</Fade>
</Modal>
);
}
Thank you for your help!

If you want to use a method of another component you need to create parent-child relation of components and then pass those methods as a prop to child component.
You should use/call <LoginModal /> inside File1.js where this LoginModel would take a prop value for visibility of modal such as isModalShown and then a function to close the modal such as closeModal
so when you call the component it would look like:
<LoginModal isModalShown={isModalShown} closeModal={closeModal} />
You need to maintain the state variable in File1.js since showing/hiding modal is done in the same file.
import React from "react";
import { IconButton, Menu, MenuItem } from "#material-ui/core";
import AccountCircleIcon from "#material-ui/icons/AccountCircle";
import LoginModal from "../components/LoginModal";
export default function AccountNotRegistered() {
const [anchorEl, setAnchorEl] = React.useState(null);
const [isModalShown, toggleModal] = React.useState(false);
const closeModal = () => toggleModal(false)
const openModal = () => toggleModal(false)
const isMenuOpen = Boolean(anchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleProfileMenuClose = () => {
setAnchorEl(null);
};
const menuId = "account-menu";
const renderMenu = (
<React.Fragment>
<Menu
anchorEl={anchorEl}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
id={menuId}
keepMounted
transformOrigin={{ vertical: "top", horizontal: "right" }}
open={isMenuOpen}
onClose={handleProfileMenuClose}>
<MenuItem onClick={openModal}>Login</MenuItem>
<MenuItem onClick={handleProfileMenuClose}>Register</MenuItem>
</Menu>
</React.Fragment>
);
return (
<div>
<IconButton color="inherit" onClick={handleProfileMenuOpen}>
<AccountCircleIcon />
</IconButton>
<LoginModal isModalShown={isModalShown} closeModal={closeModal} />
{renderMenu}
</div>
);
}
and then in the LoginModal.js
export default function LoginModal(props) {
const classes = useStyles();
return (
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={props.isModalShown}
onClose={props.closeModal}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">Transition modal</h2>
<p id="transition-modal-description">
react-transition-group animates me.
</p>
</div>
</Fade>
</Modal>
);
}
I believe you want to show the modal on click of Register as well, you can create a wrapper for this Modal component and then pass custom props to it, which would then takes care of rendering <LoginModal /> or <RegisterModal />

Related

material-ui backdrop -- fails with invalid hook call

I have been trying to use material ui backdrop element in my code. I am referring to this example: https://mui.com/components/backdrop/#example
As soon as I introduce this code into my component, it fails with error message as below.
Unhandled Runtime Error Error: Invalid hook call. Hooks can only be
called inside of the body of a function component. This could happen
for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
My code is as below.
import React, { useState } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import { Box,Typography } from '#material-ui/core'
import CategoryCard from '../shop-categories/category-card'
import { useSelector } from 'react-redux';
//import NavigateBeforeIcon from '#material-ui/icons/NavigateBefore';
//import ProductsInCategory from '../../../pages/shops/product-in-category';
import {useRouter} from 'next/router'
import Backdrop from '#mui/material/Backdrop'
import CircularProgress from '#mui/material/CircularProgress';
import { withStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
heading : {
fontFamily: 'Roboto',
fontSize: 14,
fontStyle: 'normal',
fontWeight: 700,
lineHeight: 1,
color: props => props.pColor
}
})
export default function CategoryGrid(props) {
const { categories, shopId, title} = props;
const shopColors = useSelector(state => state.main.currentShop)
const classes = useStyles(shopColors);
const router = useRouter()
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
const gotoSubCategories = (shopId, id,cat_Name) => {
setOpen(true)
router.push(`/shops/shop-sub-categories?shop=${shopId}&category=${id}&categoryName=${cat_Name}`)
}
return (
<>
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
<Typography variant="body2" style={{ fontWeight: 'bold' }}
className={classes.heading}>
{title}
</Typography>
<Box display="flex" mb={2} mx={1} flexDirection="column">
<Box display="flex" fontWeight='fontWeightBold' width={1/2} mb={1}>
</Box>
<Box display="flex" flexWrap="wrap">
{
categories.map((category, index) => {
return <CategoryCard key={index}
name={category.name}
onClick={() => gotoSubCategories(shopId, category.id,category.name)}
imageURL={category.url}
shopId={shopId} callParent={props.callParent}
/>
})
}
</Box>
</Box>
</>
)
}
If I remove out this portion, then it works fine.
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
How to fix this?

Unexpected keyword 'true' while using `useState` in react?

Here i am trying to set the open prop of the MUIDrawer to true when the user clicks it but while setting the state i am getting an error "Unexpected keyword 'true' "
import React, { useState } from "react";
import { withRouter } from "react-router-dom";
import {
Drawer as MUIDrawer,
ListItem,
List,
ListItemIcon,
ListItemText,
AppBar,
Toolbar,
IconButton
} from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import MenuIcon from "#material-ui/icons/Menu";
const useStyles = makeStyles({
drawer: {
width: "190px"
}
});
const Drawer = props => {
const { history } = props;
const classes = useStyles();
const itemsList = [
{
text: "Home",
icon: <InboxIcon />,
onClick: () => history.push("/")
},
{
text: "About",
icon: <MailIcon />,
onClick: () => history.push("/about")
},
{
text: "Contact",
icon: <MailIcon />,
onClick: () => history.push("/contact")
}
];
[state, setState] = useState(false);
const toggleDrawer = {setState(true)}
return (
<>
<AppBar>
<Toolbar>
<IconButton
style={{ position: "absolute", right: "0" }}
onClick={toggleDrawer}
>
<MenuIcon />
</IconButton>
</Toolbar>
</AppBar>
<MUIDrawer
className={classes.drawer}
open={state}
>
<List>
{itemsList.map((item, index) => {
const { text, icon, onClick } = item;
return (
<ListItem button key={text} onClick={onClick}>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText primary={text} />
</ListItem>
);
})}
</List>
</MUIDrawer>
</>
);
};
export default withRouter(Drawer);
The errors are in:
[state, setState] = useState(false);
const toggleDrawer = {setState(true)}
First you forgot the const keyword in the useState hook.
const [state, setState] = useState(false);
And the toggleDrawer must be a function you can do that like the following:
const toggleDrawer = () => {setState(true)}
or
function toggleDrawer(){
setState(true)
}
If you want, you can make the function inside the onClick:
<IconButton
style={{ position: "absolute", right: "0" }}
onClick={()=>{setState(true)}}
>
And finally if you want to make it false when you click it again:
<IconButton
style={{ position: "absolute", right: "0" }}
onClick={()=>{setState(!state)}}
>
In this last case setState(!state) will allow you to save the opposite of state.
Then, for each click you make, the state value will change to the opposite of the previous value.
Try declaring toggleDrawer as a function, like this:
const toggleDrawer = () => setState(true)

Switch function in react component not working as logic would suggest

So my react code does not execute as logic would suggest:
I am trying to create a page that resembles an email app.
I have components in React which I import into my message(main) component (e.g. Inbox component, send mail component etc). I have a useState, which is set to 'inbox' as default and a switch statement which checks which value is in the state and returns the component matching that case. I also have a onclick function which changes the state value based on which email tab you want to view (e.g. Inbox or trash etc). When the state changes the component should run the switch statement in my page render and display the correct component in the main component, which is does most of the time.
My problem comes when I keep switching between the tabs which have components it at some point goes to the default of the switch statement which does not make sense because either of the values should have passed.
Please help
import React, {useState} from 'react';
// Components to be displayed
import Inbox from './Inbox';
import SendMessage from './SendMessage';
// Other components on the page which you can ignore
import Navbar from '../Layout/Navbar';
import Copyright from '../Layout/Copyright';
import SendMail from './SendMail';
//Material ui for styling
import AppBar from '#material-ui/core/AppBar';
import CssBaseline from '#material-ui/core/CssBaseline';
import Divider from '#material-ui/core/Divider';
import Drawer from '#material-ui/core/Drawer';
import Hidden from '#material-ui/core/Hidden';
import IconButton from '#material-ui/core/IconButton';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import MailIcon from '#material-ui/icons/Mail';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import Link from '#material-ui/core/Link';
import ControlPointIcon from '#material-ui/icons/ControlPoint';
const drawerWidth = 240;
const list = [
{id: 1, title: 'Singing lessons', message: 'We are practicing at 5:00'},
{id: 2, title: 'Meeting', message: 'Hi Guys, we can meet during lunch at Nandos'},
{id: 3, title: 'New Product release', message: 'Hi guys, we are encouraging everyone to buy in on the new product'}
]
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex'
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
zIndex: '0'
}
},
appBar: {
[theme.breakpoints.up('sm')]: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
},
menuButton: {
marginRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
display: 'none',
},
},
// necessary for content to be below app bar
toolbar: theme.mixins.toolbar,
drawerPaper: {
width: drawerWidth,
},
content: {
flexGrow: 1,
padding: theme.spacing(3)
},
listItemStyle: {
marginTop: '50px'
},
newMessage: {
fontSize: '40px',
backgroundColor: '#64b5f6',
borderRadius: '50px',
color: '#fff',
position: 'absolute',
bottom: '50px',
right: '50px',
'&:hover': {
backgroundColor: '#1976d2'
}
}
}));
// Main component
const Message = (props) => {
const { window } = props;
const classes = useStyles();
const theme = useTheme();
const [mobileOpen, setMobileOpen] = useState(false);
const [tabTracker, setTabTracker] = useState('Inbox');
const renderSwitch = (param) => {
switch(param) {
case 'Inbox':
return <Inbox />;
case 'Send email':
return <SendMail />;
case 'Drafts':
return 'Draft';
case 'Trash':
return 'Trash';
case 'Spam':
return 'Spam';
case 'newMessage':
return <SendMessage />;
default:
return 'foo';
}
}
const tabControl = (e) => {
setTabTracker(e.target.firstChild.data);
//renderSwitch(tabTracker);
}
const newMsg = (e) => {
setTabTracker(e.target.attributes[5].value);
//renderSwitch(tabTracker);
}
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const container = window !== undefined ? () => window().document.body : undefined;
return (
<React.Fragment>
<span>
<Navbar />
</span>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer} aria-label="mailbox folders">
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Hidden smUp implementation="css">
<Drawer
container={container}
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={mobileOpen}
onClose={handleDrawerToggle}
classes={{
paper: classes.drawerPaper,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer
classes={{
paper: classes.drawerPaper,
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{renderSwitch(tabTracker)}
<Link href="#">
<ControlPointIcon value="newMessage" primary="newMessage" className= {classes.newMessage} onClick={newMsg} />
</Link>
</main>
</div>
<Copyright />
</React.Fragment>
);
}
// ResponsiveDrawer.propTypes = {
// /**
// * Injected by the documentation to work in an iframe.
// * You won't need it on your project.
// */
// window: PropTypes.func,
// };
export default Message;
e.target gives you the element on which click was actually triggered, so when you have multiple elements as children within ListItem, its possible that you would have clicked on ListItemIcon and hence e.target is listItemIcon which was not your intention as it will give you an incorrect value for e.target.firstChild.data
So you could either use e.currentTarget which will give you the element on which the onClick listener is attached or you could simply pass on the information to tabControl by using an inline arrow function
const tabControl = (value) => {
setTabTracker(value);
}
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);

How to keep showing the 'popover' on hovering on the anchorEl and 'popover' as well?

Here in this example of material-ui https://material-ui.com/utils/popover/#mouse-over-interaction
Follow these steps for the example material-ui https://material-ui.com/utils/popover/#mouse-over-interaction
In the above example keep the mouse on the text Hover with a Popover.
--- you see the popover
Try to move your mouse near the popover --- popover disappears right?
But I wanna show the popover even if I hover on popover
And make popover disappear only if the user is not hovering on either popover or the Hover with a Popover. (basically anchorEl)
I am copying code from their demo
import React from 'react';
import PropTypes from 'prop-types';
import Popover from '#material-ui/core/Popover';
import Typography from '#material-ui/core/Typography';
import { withStyles } from '#material-ui/core/styles';
const styles = theme => ({
popover: {
pointerEvents: 'none',
},
paper: {
padding: theme.spacing.unit,
},
});
class MouseOverPopover extends React.Component {
state = {
anchorEl: null,
};
handlePopoverOpen = event => {
this.setState({ anchorEl: event.currentTarget });
};
handlePopoverClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { classes } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.paper,
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={this.handlePopoverClose}
disableRestoreFocus
>
<Typography>I use Popover.</Typography>
</Popover>
</div>
);
}
}
MouseOverPopover.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(MouseOverPopover);
What code change I need to make here?
You may experiment https://codesandbox.io/s/6l3wk6kv3
I had the same problem, didn't find any answer and I took a while to understand how to fix it.
Actually the problem comes from the pointerEvents: none that you need on the popover to prevent your onMouseEnter/onMouseLeave to be triggered at the same time.
But you can set for the content of your popover pointerEvents: auto.
Then you can add a onMouseEnter and a onMouseLeave on the content of your popover.
Here is an exemple to make it more explicit :
import React, { useState, useRef } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { Popover } from '#material-ui/core';
const useStyles = makeStyles(theme => ({
popover: {
pointerEvents: 'none',
},
popoverContent: {
pointerEvents: 'auto',
},
}));
const MyComponent = ({ loading, login, wrong, clearWrongLogin }: Props) => {
const [openedPopover, setOpenedPopover] = useState(false)
const popoverAnchor = useRef(null);
const popoverEnter = ({ currentTarget }) => {
setOpenedPopover(true)
};
const popoverLeave = ({ currentTarget }) => {
setOpenedPopover(false)
};
const classes = useStyles();
return (
<div>
<span
ref={popoverAnchor}
aria-owns="mouse-over-popover"
aria-haspopup="true"
onMouseEnter={popoverEnter}
onMouseLeave={popoverLeave}
>Hover this el !
</span>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.popoverContent,
}}
open={openedPopover}
anchorEl={popoverAnchor.current}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{onMouseEnter: popoverEnter, onMouseLeave: popoverLeave}}
>
<div>
My popover content...
</div>
</Popover>
</div>
);
};
export default MyComponent
In MUI v5 :
I searched for the solution a lot, and the easiest way is using "material-ui-popup-state". Be sure because it was introduced by MUI here;
I made a complete example codesandbox
import * as React from 'react';
import Typography from '#mui/material/Typography';
import Button from '#mui/material/Button';
import PopupState, { bindTrigger, bindPopover ,bindHover } from 'material-ui-popup-state';
import HoverPopover from "material-ui-popup-state/HoverPopover";
export default function PopoverPopupState() {
return (
<PopupState variant="popover" popupId="demo-popup-popover">
{(popupState) => (
<div>
<Button variant="contained" {...bindHover(popupState)}>
Open Popover
</Button>
<HoverPopover
{...bindPopover(popupState)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<Typography sx={{ p: 2 }}>The content of the Popover.</Typography>
</HoverPopover>
</div>
)}
</PopupState>
);
}
import React from "react";
import Popover from "#material-ui/core/Popover";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
popover: {
pointerEvents: "none"
},
paper: {
pointerEvents: "auto",
padding: theme.spacing(1)
}
}));
export default function MouseOverPopover() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = event => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<div onMouseEnter={handlePopoverOpen} onMouseLeave={handlePopoverClose}>
<Typography
aria-owns={open ? "mouse-over-popover" : undefined}
aria-haspopup="true"
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.paper
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography>I use Popover.</Typography>
</Popover>
</div>
);
}
MUI v5
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleMouseLeave}
sx={{
pointerEvents: 'none'
}}
MenuListProps={{
'aria-labelledby': 'basic-button'
}}
PaperProps={{
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
sx: {
pointerEvents: 'auto'
}
}}
>
<MenuItem onClick={handleMouseLeave}>Profile</MenuItem>
<MenuItem onClick={handleMouseLeave}>My account</MenuItem>
<MenuItem onClick={handleMouseLeave}>Logout</MenuItem>
</Menu>
MUI v5
export const MouseOverPopover = () => {
const [openedPopover, setOpenedPopover] = useState(false);
const popoverAnchor = useRef(null);
const popoverEnter = () => {
setOpenedPopover(true);
};
const popoverLeave = () => {
setOpenedPopover(false);
};
return (
<>
<Button
aria-haspopup="true"
onMouseEnter={popoverEnter}
onMouseLeave={popoverLeave}
ref={popoverAnchor}
/>
<Popover
open={openedPopover}
anchorEl={popoverAnchor.current}
sx={{
pointerEvents: "none",
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
PaperProps={{ onMouseEnter: popoverEnter, onMouseLeave: popoverLeave }}
>
<Box sx={{ pointerEvents: "auto" }}>{content}</Box>
</Popover>
</>
);
};
Thanks for this answer. Be careful of using pointerEvents: 'none' and pointerEvents: 'auto' to works truly.
codesandbox DEMO
I hacked my way through it by adding a setTimeout() function for the onMouseLeave event...I am sure that there is other ways of doing it, but it depends on your specific needs
handlePopoverClose = () => {
setTimeout(() => {
this.setState({ anchorEl: null });
}, 3000);
};

How to assign which MenuItems open onClick when multiple Menus are present on the material-ui Appbar using React?

I created an AppBar with a menu based on the examples given on the material UI site and it works fine with one menu. But when I try adding a second dropdown menu, on clicking either icon, I get the same set of MenuItems showing up as seen in the image.
Here is the list of menu items that are showing up when either icon is clicked
import React, { Component } from 'react';
// Material UI Imports
import { withStyles } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';
import Tooltip from 'material-ui/Tooltip';
import Menu, { MenuItem } from 'material-ui/Menu';
import PeopleIcon from 'material-ui-icons/People';
import ViewListIcon from 'material-ui-icons/ViewList';
import CompareArrowsIcon from 'material-ui-icons/CompareArrows';
const styles = {
root: {
width: '100%',
},
flex: {
flex: 1,
},
menuItem: {
paddingLeft: '10px'
}
}
class Header extends Component {
constructor(props) {
super(props);
this.state = { anchorEl: null };
}
handleMenu = event => {
console.log(event.currentTarget);
this.setState({ anchorEl: event.currentTarget });
}
handleClose = () => {
this.setState({ anchorEl: null });
}
render() {
const { classes } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return(
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography type="title" color="inherit" className={classes.flex}>
New Faces
</Typography>
{/* Menu Item One */}
<div>
<Tooltip title="Lists" className={classes.menuItem}>
<IconButton
color="inherit"
aria-owns={open ? 'menu-list' : null}
aria-haspopup="true"
onClick={this.handleMenu}
>
<ViewListIcon />
</IconButton>
</Tooltip>
<Menu
id="menu-list"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal:'right',
}}
transformOrigin={{
vertical: 'top',
horizontal:'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Create List</MenuItem>
<MenuItem onClick={this.handleClose}>List 1</MenuItem>
<MenuItem onClick={this.handleClose}>List 2</MenuItem>
</Menu>
</div>
{/* Menu Item Two */}
<div>
<Tooltip title="User Management" className={classes.menuItem}>
<IconButton
color="inherit"
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup="true"
onClick={this.handleMenu}
>
<PeopleIcon />
</IconButton>
</Tooltip>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal:'right',
}}
transformOrigin={{
vertical: 'top',
horizontal:'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>User Management</MenuItem>
<MenuItem onClick={this.handleClose}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
</div>
);
}
}
export default withStyles(styles)(Header);
How do you assign which MenuItems show up according to the icon that is clicked? I assumed it was supposed to show the MenuItems that are directly under the selected anchorEl. Any help would be much appreciated!
Check this working solution https://codesandbox.io/s/84xl2v8wm2 I whipped up
What I have done is extract the common code into a separate component lets call it MenuButton and use it at multiple places. So that each menu button has its own scope, own event handlers etc. Currently, the issue is that you are using same event handlers for two different elements and using the same variable to keep track of the state of the menu. Either use two variables like open and open1 or extract the code into a separate component like I have done.
Parent file
<MenuButton iconType={AccountCircle} items={['Create','List1', 'List2']}/>
<MenuButton iconType={MenuIcon} items={['Profile','User Management', 'Logout']}/>
menuButton.js file
import React from 'react';
import AccountCircle from 'material-ui-icons/AccountCircle';
import Menu, { MenuItem } from 'material-ui/Menu';
import IconButton from 'material-ui/IconButton';
import { withStyles } from 'material-ui/styles';
class MenuButton extends React.Component {
state = {
anchorEl: null
};
handleChange = (event, checked) => {
this.setState({ auth: checked });
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { classes } = this.props;
const { auth, anchorEl } = this.state;
const open = Boolean(anchorEl);
const Wrapper = this.props.iconType;
const listItems = this.props.items.map((link) =>
<MenuItem onClick={this.handleClose} >{link}</MenuItem>
);
return (
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup="true"
onClick={this.handleMenu}
color="contrast"
>
{<Wrapper />}
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}
>
{listItems}
</Menu>
</div>
);
}
}
export default MenuButton;

Categories

Resources