Detect click outside React component

后端 未结 30 1210
日久生厌
日久生厌 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:02

    I was stuck on the same issue. I am a bit late to the party here, but for me this is a really good solution. Hopefully it will be of help to someone else. You need to import findDOMNode from react-dom

    import ReactDOM from 'react-dom';
    // ... ✂
    
    componentDidMount() {
        document.addEventListener('click', this.handleClickOutside, true);
    }
    
    componentWillUnmount() {
        document.removeEventListener('click', this.handleClickOutside, true);
    }
    
    handleClickOutside = event => {
        const domNode = ReactDOM.findDOMNode(this);
    
        if (!domNode || !domNode.contains(event.target)) {
            this.setState({
                visible: false
            });
        }
    }
    

    React Hooks Approach (16.8 +)

    You can create a reusable hook called useComponentVisible.

    import { useState, useEffect, useRef } from 'react';
    
    export default function useComponentVisible(initialIsVisible) {
        const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
        const ref = useRef(null);
    
        const handleClickOutside = (event) => {
            if (ref.current && !ref.current.contains(event.target)) {
                setIsComponentVisible(false);
            }
        };
    
        useEffect(() => {
            document.addEventListener('click', handleClickOutside, true);
            return () => {
                document.removeEventListener('click', handleClickOutside, true);
            };
        });
    
        return { ref, isComponentVisible, setIsComponentVisible };
    }
    

    Then in the component you wish to add the functionality to do the following:

    const DropDown = () => {
        const { ref, isComponentVisible } = useComponentVisible(true);
        return (
           <div ref={ref}>
              {isComponentVisible && (<p>Dropdown Component</p>)}
           </div>
        );
    
    }
    

    Find a codesandbox example here.

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

    I used this module (I have no association with the author)

    npm install react-onclickout --save
    

    const ClickOutHandler = require('react-onclickout');
     
    class ExampleComponent extends React.Component {
     
      onClickOut(e) {
        if (hasClass(e.target, 'ignore-me')) return;
        alert('user clicked outside of the component!');
      }
     
      render() {
        return (
          <ClickOutHandler onClickOut={this.onClickOut}>
            <div>Click outside of me!</div>
          </ClickOutHandler>
        );
      }
    }

    It did the job nicely.

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

    I found a solution thanks to Ben Alpert on discuss.reactjs.org. The suggested approach attaches a handler to the document but that turned out to be problematic. Clicking on one of the components in my tree resulted in a rerender which removed the clicked element on update. Because the rerender from React happens before the document body handler is called, the element was not detected as "inside" the tree.

    The solution to this was to add the handler on the application root element.

    main:

    window.__myapp_container = document.getElementById('app')
    React.render(<App/>, window.__myapp_container)
    

    component:

    import { Component, PropTypes } from 'react';
    import ReactDOM from 'react-dom';
    
    export default class ClickListener extends Component {
    
      static propTypes = {
        children: PropTypes.node.isRequired,
        onClickOutside: PropTypes.func.isRequired
      }
    
      componentDidMount () {
        window.__myapp_container.addEventListener('click', this.handleDocumentClick)
      }
    
      componentWillUnmount () {
        window.__myapp_container.removeEventListener('click', this.handleDocumentClick)
      }
    
      /* using fat arrow to bind to instance */
      handleDocumentClick = (evt) => {
        const area = ReactDOM.findDOMNode(this.refs.area);
    
        if (!area.contains(evt.target)) {
          this.props.onClickOutside(evt)
        }
      }
    
      render () {
        return (
          <div ref='area'>
           {this.props.children}
          </div>
        )
      }
    }
    
    0 讨论(0)
  • 2020-11-22 14:06

    For those who need absolute positioning, a simple option I opted for is to add a wrapper component that is styled to cover the whole page with a transparent background. Then you can add an onClick on this element to close your inside component.

    <div style={{
            position: 'fixed',
            top: '0', right: '0', bottom: '0', left: '0',
            zIndex: '1000',
          }} onClick={() => handleOutsideClick()} >
        <Content style={{position: 'absolute'}}/>
    </div>
    

    As it is right now if you add a click handler on content, the event will also be propagated to the upper div and therefore trigger the handlerOutsideClick. If this is not your desired behavior, simply stop the event progation on your handler.

    <Content style={{position: 'absolute'}} onClick={e => {
                                              e.stopPropagation();
                                              desiredFunctionCall();
                                            }}/>
    

    `

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

    I found this from the article below:

    render() { return ( { this.node = node; }} > Toggle Popover {this.state.popupVisible && ( I'm a popover! )} ); } }

    Here is a great article about this issue: "Handle clicks outside of React components" https://larsgraubner.com/handle-outside-clicks-react/

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

    None of the other answers here worked for me. I was trying to hide a popup on blur, but since the contents were absolutely positioned, the onBlur was firing even on the click of inner contents too.

    Here is an approach that did work for me:

    // Inside the component:
    onBlur(event) {
        // currentTarget refers to this component.
        // relatedTarget refers to the element where the user clicked (or focused) which
        // triggered this event.
        // So in effect, this condition checks if the user clicked outside the component.
        if (!event.currentTarget.contains(event.relatedTarget)) {
            // do your thing.
        }
    },
    

    Hope this helps.

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