Detect click outside React component

后端 未结 30 1188
日久生厌
日久生厌 2020-11-22 13:54

I\'m looking for a way to detect if a click event happened outside of a component, as described in this article. jQuery closest() is used to see if the target from a click e

相关标签:
30条回答
  • 2020-11-22 14:14

    you can you a simple way to solve your problem , i show you :

    ....

    const [dropDwonStatus , setDropDownStatus] = useState(false)
    
    const openCloseDropDown = () =>{
     setDropDownStatus(prev => !prev)
    }
    
    const closeDropDown = ()=> {
     if(dropDwonStatus){
       setDropDownStatus(false)
     }
    }
    .
    .
    .
    <parent onClick={closeDropDown}>
     <child onClick={openCloseDropDown} />
    </parent>
    

    this works for me , good luck ;)

    0 讨论(0)
  • 2020-11-22 14:14

    https://stackoverflow.com/a/42234988/9536897 it's not work on mobile mode.

    than you can try:

      // returns true if the element or one of its parents has the class classname
      hasSomeParentTheClass(element, classname) {
        if(element.target)
        element=element.target;
        
        if (element.className&& element.className.split(" ").indexOf(classname) >= 0) return true;
        return (
          element.parentNode &&
          this.hasSomeParentTheClass(element.parentNode, classname)
        );
      }
    
      componentDidMount() {
        const fthis = this;
    
        $(window).click(function (element) {
          if (!fthis.hasSomeParentTheClass(element, "myClass"))
            fthis.setState({ pharmacyFocus: null });
        });
      }
    
    • On the view, gave className to your specific element.
    0 讨论(0)
  • 2020-11-22 14:15

    My biggest concern with all of the other answers is having to filter click events from the root/parent down. I found the easiest way was to simply set a sibling element with position: fixed, a z-index 1 behind the dropdown and handle the click event on the fixed element inside the same component. Keeps everything centralized to a given component.

    Example code

    #HTML
    <div className="parent">
      <div className={`dropdown ${this.state.open ? open : ''}`}>
        ...content
      </div>
      <div className="outer-handler" onClick={() => this.setState({open: false})}>
      </div>
    </div>
    
    #SASS
    .dropdown {
      display: none;
      position: absolute;
      top: 0px;
      left: 0px;
      z-index: 100;
      &.open {
        display: block;
      }
    }
    .outer-handler {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        opacity: 0;
        z-index: 99;
        display: none;
        &.open {
          display: block;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 14:15

    I had a case when I needed to insert children into the modal conditionally. Something like this, bellow.

    const [view, setView] = useState(VIEWS.SomeView)
    
    return (
        <Modal onClose={onClose}>
          {VIEWS.Result === view ? (
            <Result onDeny={() => setView(VIEWS.Details)} />
          ) : VIEWS.Details === view ? (
            <Details onDeny={() => setView(VIEWS.Result) /> />
          ) : null}
        </Modal>
      )
    

    So !parent.contains(event.target) doesn't work here, because once you detach children, parent (modal) doesn't contain event.target anymore.

    The solution I had (which works so far and have no any issue) is to write something like this:

    const listener = (event: MouseEvent) => {
       if (parentNodeRef && !event.path.includes(parentNodeRef)) callback()
    }
    

    If parent contained element from already detached tree, it wouldn't fire callback.

    EDIT: event.path is new and doesn't exit in all browsers yet. Use compoesedPath instead.

    0 讨论(0)
  • 2020-11-22 14:16

    [Update] Solution with React ^16.8 using Hooks

    CodeSandbox

    import React, { useEffect, useRef, useState } from 'react';
    
    const SampleComponent = () => {
        const [clickedOutside, setClickedOutside] = useState(false);
        const myRef = useRef();
    
        const handleClickOutside = e => {
            if (!myRef.current.contains(e.target)) {
                setClickedOutside(true);
            }
        };
    
        const handleClickInside = () => setClickedOutside(false);
    
        useEffect(() => {
            document.addEventListener('mousedown', handleClickOutside);
            return () => document.removeEventListener('mousedown', handleClickOutside);
        });
    
        return (
            <button ref={myRef} onClick={handleClickInside}>
                {clickedOutside ? 'Bye!' : 'Hello!'}
            </button>
        );
    };
    
    export default SampleComponent;
    

    Solution with React ^16.3:

    CodeSandbox

    import React, { Component } from "react";
    
    class SampleComponent extends Component {
      state = {
        clickedOutside: false
      };
    
      componentDidMount() {
        document.addEventListener("mousedown", this.handleClickOutside);
      }
    
      componentWillUnmount() {
        document.removeEventListener("mousedown", this.handleClickOutside);
      }
    
      myRef = React.createRef();
    
      handleClickOutside = e => {
        if (!this.myRef.current.contains(e.target)) {
          this.setState({ clickedOutside: true });
        }
      };
    
      handleClickInside = () => this.setState({ clickedOutside: false });
    
      render() {
        return (
          <button ref={this.myRef} onClick={this.handleClickInside}>
            {this.state.clickedOutside ? "Bye!" : "Hello!"}
          </button>
        );
      }
    }
    
    export default SampleComponent;
    
    0 讨论(0)
  • 2020-11-22 14:17

    Material-UI has a small component to solve this problem: https://material-ui.com/components/click-away-listener/ that you can cherry-pick it. It weights 1.5 kB gzipped, it supports mobile, IE 11 and portals.

    0 讨论(0)
提交回复
热议问题