NaNodine
NaNodine

Reputation: 313

Close material-ui popper when on clickAway

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

Answers (4)

Taha
Taha

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

Christopher
Christopher

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

Clom
Clom

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

Yevhen Horbunkov
Yevhen Horbunkov

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

Related Questions