Reputation: 313
I have a material ui popper and I am trying to make it close when I click outside of the popper using ClickAwayListener, but I cannot get this to work. I added the ClickAwayListener around the popper and tried adding it around the content in the popper but nothing seams to work.
I am really new to material-ui so I am a bit lost on how this should be done
This is my code
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
// const open = Boolean(anchorEl);
const handleClickAway = () => {
setAnchorEl(false);
};
const experience = (img, title, id, popoverCategory) => (
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
<ClickAwayListener onClickAway={handleClickAway}>
<Popper
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
className={clsx(classes[id])}
modifiers={{
flip: {
enabled: false,
},
}}
>
<Button >x</Button>
<div className={clsx(classes.paper)}>
{
popoverCategory.map(url => (
<img
key={id}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</Popper>
</ClickAwayListener>
</div>
);
Upvotes: 18
Views: 31379
Reputation: 1242
I got the same issue I think wrapping elements inside popper with a ClickAwayListener looks a simple solution
Example:
const ParentComponent = () => {
const [accountMenuBtnEL, setAccountMenuBtnEL] = useState(null);
const handleClickOnAccountBtn = (ev: React.MouseEvent<any>) => {
setAccountMenuBtnEL(ev.currentTarget);
};
const handleCloseAccountMenu = () => {
setAccountMenuBtnEL(null);
};
return (
<>
<Button onClick={handleClickOnAccountBtn}>
Click
</Button>
<PopperMenu anchorEl={accountMenuBtnEL} handleClose={handleCloseAccountMenu} />
</>
);
};
const PopperMenu: React.FC<{ anchorEl: Element; handleClose: () => void }> = ({ anchorEl, handleClose }) => {
return (
<Popper id='account-menu' anchorEl={anchorEl} open={Boolean(anchorEl)} placement='bottom-end' sx={{ p: 1 }}>
<ClickAwayListener onClickAway={handleClose}>
<Box display='flex' flexDirection='column'>
<Button variant='contained'>Connexion</Button>
</Box>
</ClickAwayListener>
</Popper>
);
};
Upvotes: 2
Reputation: 41
Perhaps MUI has created a new component that handles this for us since the time of this post.
Popover Component: https://mui.com/material-ui/react-popover/
It works similar to the Modal component that handles on click out for you.
Hope this helps.
Upvotes: 4
Reputation: 371
It is not working because por the Popper component is a Portal and with portals you have to use de ClickAwayListener in a small different way.
You can check the example at MUI documentation but the short summary is that the button that opens the popper has to be inside the ClickAwayListener too and you have to check if it's open before rendering the Popper.
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const open = Boolean(anchorEl);
const handleClickAway = () => {
setAnchorEl(false);
};
const experience = (img, title, id, popoverCategory) => (
<ClickAwayListener onClickAway={handleClickAway}> // This wraps all
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
{open ? ( // You do the check here
<Popper
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
className={clsx(classes[id])}
modifiers={{
flip: {
enabled: false,
},
}}
>
<Button >x</Button>
<div className={clsx(classes.paper)}>
{
popoverCategory.map(url => (
<img
key={id}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</Popper>
) : null}
</div>
</ClickAwayListener>
);
Upvotes: 4
Reputation: 15530
You may toggle your <Popper />
component visibility using the variable in the local state of the parent component and pass it down as a prop:
//dependencies
const { render } = ReactDOM,
{ useState } = React,
{ Popper, Button, Paper, ClickAwayListener } = MaterialUI
//custom popper
const MyPopper = ({isOpen,clickAwayHandler}) => (
<ClickAwayListener onClickAway={clickAwayHandler}>
<Popper open={isOpen}>
<Paper className="popper">There goes my custom popper</Paper>
</Popper>
</ClickAwayListener>
)
//main page
const MainPage = () => {
const [isOpen, setIsOpen] = useState(true),
clickAwayHandler = () => setIsOpen(false),
clickHandler = () => setIsOpen(true)
return (
<div>
<Button onClick={clickHandler}>Toggle pop-up</Button>
{
isOpen && <MyPopper {...{clickAwayHandler,isOpen}} />
}
</div>
)
}
//render
render (
<MainPage />,
document.getElementById('root')
)
.popper {
display: block;
position: relative;
top: 50px;
left: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script><div id="root"></div>
Upvotes: 21