this.wrapperRef.current.contains(element) returns false although element is a grand child

Viewed 438

A wrapper EditModal component comes with a onClickOutside event. A child component of this modal is a Material-UI Select. A click on a MenuItem fires onClickOutside thus closing the modal without the new value being selected.

The source of the problem is this.wrapperRef.current.contains(element) returning false even though MenuItem is a grand child of EditModal.

Why is this happening? How to avoid this behavior?

https://codesandbox.io/s/select-inside-edit-modal-l69zj?file=/src/edit-modal.js (code is adapted from this blog)

enter image description here

import React, { createRef } from "react";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";

class EditModal extends React.Component {
  constructor(props) {
    super(props);
    this.wrapperRef = createRef();
    this.state = { value: props.initialValue };
  }

  componentDidMount() {
    document.body.addEventListener("mousedown", this.onClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener("mousedown", this.onClickOutside);
  }

  onClickOutside = (e) => {
    const { onClose } = this.props;
    const element = e.target;

    if (this.wrapperRef.current && !this.wrapperRef.current.contains(element)) {
      console.log(this.wrapperRef.current.contains(element));
      e.preventDefault();
      e.stopPropagation();
      onClose();
    }
  };

  onChange = (e) => {
    const { onClose } = this.props;

    this.setState({ value: e.target.value });
    this.props.onChange(e.target.value);
    onClose();
  };

  render() {
    const value = this.state.value;

    return (
      <div className="modal--overlay">
        <div className="modal" ref={this.wrapperRef}>
          <h1>Select a new value</h1>
          <Select
            id="demo-customized-select"
            value={value}
            color="primary"
            onChange={this.onChange}
          >
            <MenuItem value={"A"}>A</MenuItem>
            <MenuItem value={"B"}>B</MenuItem>
            <MenuItem value={"C"}>C</MenuItem>
          </Select>
        </div>
      </div>
    );
  }
}

export default EditModal;
1 Answers

A colleague found the answer. The popover menu generated by Material-UI is not placed as children of the Select in the DOM.

To fix the onClickOutside, an additionnal condition referencing this menu should be added using Select attribute MenuProps.

https://codesandbox.io/s/select-inside-edit-modal-forked-wrz7t?file=/src/edit-modal.js:1382-1448

  [...]

    this.wrapperRef = createRef();
    this.inputRef = createRef();

  [...]

    if (
       this.wrapperRef.current &&
       !this.wrapperRef.current.contains(element) &&
       (this.inputRef.current === null ||
       (this.inputRef.current && !this.inputRef.current.contains(element)))
       ) {

  [...]
          <Select
            MenuProps={{
              ref: this.inputRef
            }}
            id="demo-customized-select"
            value={value}
            color="primary"
            onChange={this.onChange}
          >

  [...]
Related