Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Material UI ClickAwayListener close when clicking itself

I have the below display sidebar Switch that shows up within a Popper. So, Ideally, if you click elsewhere (outside of the Popper element), Popper should disappear. If you click inside Popper element, it should not go anywhere. When I click on the Switch or Display Sidebar text, that Popper goes away. I wrapped the Popper with <div> it didn't help either.

Popper https://material-ui.com/api/popper/

Switch https://material-ui.com/api/switch/

ClickAwayListener https://material-ui.com/utils/click-away-listener/

Below is the Popper Code

<ClickAwayListener onClickAway={this.handleClickAway}>
      <div>
        <Popper className={classes.popper} id={id} open={open} placement="bottom-end" anchorEl={anchorEl} transition>
          {({ TransitionProps }) => (
            <Fade {...TransitionProps} timeout={350}>
              <Paper className={classes.SliderBox}>
                <Switch
                  checked={this.state.checkedB}
                  onChange={this.handleChange('checkedB')}
                  value="checkedB"
                  color="primary"
                  onClick={handleDrawer}
                  className={classNames(classes.menuButton, sidebar && classes.hide)}
                />
                Display Sidebar
              </Paper>
            </Fade>
          )}
        </Popper>
        </div>
       </ClickAwayListener>

I have the sample here (though I couldn't get it work I don't know why it gives error on clickaway) https://codesandbox.io/s/8pkm3x1902

enter image description here

like image 972
Extelliqent Avatar asked Mar 13 '19 14:03

Extelliqent


1 Answers

By default, Popper leverages a portal (https://github.com/mui-org/material-ui/blob/v4.11.0/packages/material-ui/src/Popper/Popper.js#L202) which renders its content in a separate part of the DOM (as a direct child of the <body> element) from where the Popper element is. This means that you have the ClickAwayListener around an empty <div>, so a click anywhere (including within the Popper content) will be considered to be outside of that empty <div>.

Moving the ClickAwayListener directly around the Fade (rather than around the Popper) ensures that it is surrounding the actual content rendered by the Popper.

Here's a working example based on your sandbox:

import React, { Component } from "react";
import ReactDOM from "react-dom";
import Popper from "@material-ui/core/Popper";
import Fade from "@material-ui/core/Fade";
import Paper from "@material-ui/core/Paper";
import Switch from "@material-ui/core/Switch";
import Avatar from "@material-ui/core/Avatar";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";

class App extends Component {
  state = {
    anchorEl: null,
    open: false,
    checkedB: true
  };

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

  handleChange = (name) => (event) => {
    this.setState({
      [name]: event.target.checked
    });
  };

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

  render() {
    const { anchorEl, open } = this.state;
    const { handleDrawer } = this.props;
    const id = open ? "simple-popper" : null;
    return (
      <div className="App">
        asdsadsa
        <Avatar
          alt="Test"
          src="https://www.nretnil.com/avatar/LawrenceEzekielAmos.png"
          style={{ margin: "0 10px" }}
          onClick={this.handleClick}
        />
        <div>
          <Popper
            id={id}
            open={open}
            placement="bottom-end"
            anchorEl={anchorEl}
            transition
          >
            {({ TransitionProps }) => (
              <ClickAwayListener onClickAway={this.handleClickAway}>
                <Fade {...TransitionProps} timeout={350}>
                  <Paper //className={classes.SliderBox}
                  >
                    <Switch
                      checked={this.state.checkedB}
                      onChange={this.handleChange("checkedB")}
                      value="checkedB"
                      color="primary"
                      onClick={handleDrawer}
                    />
                    Display Sidebar
                  </Paper>
                </Fade>
              </ClickAwayListener>
            )}
          </Popper>
        </div>
      </div>
    );
  }
}

export default App;

ReactDOM.render(<App />, document.getElementById("root"));

Edit Popper ClickAwayListener

In v4 the ClickAwayListener supports recognizing that elements are within its React element tree even when they are rendered within a portal (the original question was for v3), but it is still more reliable to put the ClickAwayListener inside the Popper rather than outside to avoid the ClickAwayListener firing on the click to open the Popper which then makes it look like the Popper isn't working since it immediately closes (example: https://codesandbox.io/s/popper-clickawaylistener-forked-x4j0l?file=/src/index.js).

like image 127
Ryan Cogswell Avatar answered Nov 19 '22 00:11

Ryan Cogswell