Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect click outside React component

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 event has the dom element as one of its parents. If there is a match the click event belongs to one of the children and is thus not considered to be outside of the component.

So in my component I want to attach a click handler to window. When the handler fires I need to compare the target with the dom children of my component.

The click event contains properties like "path" which seems to hold the dom path that the event has travelled. I'm not sure what to compare or how to best traverse it, and I'm thinking someone must have already put that in a clever utility function... No?

like image 980
Thijs Koerselman Avatar asked Sep 13 '15 18:09

Thijs Koerselman


People also ask

How do you detect click outside in a React component?

Detecting an outside click of a functional component. Let's build an HTML tooltip by creating a React functional component named InfoBox . The tooltip will appear when the user clicks a button, and it will be closed if the user clicks outside of the tooltip component.

Can touch out side a view component be detected in React native?

As Andrew said: You can wrap your View with TouchableWithoutFeedback and adding a onPress you can detect when the view is tapped.


2 Answers

Refs usage in React 16.3+ changed.

The following solution uses ES6 and follows best practices for binding as well as setting the ref through a method.

To see it in action:

  • Hooks Implementation
  • Class Implementation

Hooks Implementation:

import React, { useRef, useEffect } from "react";  /**  * Hook that alerts clicks outside of the passed ref  */ function useOutsideAlerter(ref) {     useEffect(() => {         /**          * Alert if clicked on outside of element          */         function handleClickOutside(event) {             if (ref.current && !ref.current.contains(event.target)) {                 alert("You clicked outside of me!");             }         }          // Bind the event listener         document.addEventListener("mousedown", handleClickOutside);         return () => {             // Unbind the event listener on clean up             document.removeEventListener("mousedown", handleClickOutside);         };     }, [ref]); }  /**  * Component that alerts if you click outside of it  */ export default function OutsideAlerter(props) {     const wrapperRef = useRef(null);     useOutsideAlerter(wrapperRef);      return <div ref={wrapperRef}>{props.children}</div>; } 

Class Implementation:

import React, { Component } from 'react'; import PropTypes from 'prop-types';  /**  * Component that alerts if you click outside of it  */ export default class OutsideAlerter extends Component {     constructor(props) {         super(props);          this.wrapperRef = React.createRef();         this.setWrapperRef = this.setWrapperRef.bind(this);         this.handleClickOutside = this.handleClickOutside.bind(this);     }      componentDidMount() {         document.addEventListener('mousedown', this.handleClickOutside);     }      componentWillUnmount() {         document.removeEventListener('mousedown', this.handleClickOutside);     }      /**      * Alert if clicked on outside of element      */     handleClickOutside(event) {         if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {             alert('You clicked outside of me!');         }     }      render() {         return <div ref={this.wrapperRef}>{this.props.children}</div>;     } }  OutsideAlerter.propTypes = {     children: PropTypes.element.isRequired, }; 
like image 160
Ben Bud Avatar answered Sep 18 '22 02:09

Ben Bud


2021 Update:

It has bee a while since I added this response, and since it still seems to garner some interest, I thought I would update it to a more current React version. On 2021, this is how I would write this component:

import React, { useState } from "react"; import "./DropDown.css";  export function DropDown({ options, callback }) {     const [selected, setSelected] = useState("");     const [expanded, setExpanded] = useState(false);      function expand() {         setExpanded(true);     }      function close() {         setExpanded(false);     }      function select(event) {         const value = event.target.textContent;         callback(value);         close();         setSelected(value);     }      return (         <div className="dropdown" tabIndex={0} onFocus={expand} onBlur={close} >             <div>{selected}</div>             {expanded ? (                 <div className={"dropdown-options-list"}>                     {options.map((O) => (                         <div className={"dropdown-option"} onClick={select}>                             {O}                         </div>                     ))}                 </div>             ) : null}         </div>     ); } 

Original Answer (2016):

Here is the solution that best worked for me without attaching events to the container:

Certain HTML elements can have what is known as "focus", for example input elements. Those elements will also respond to the blur event, when they lose that focus.

To give any element the capacity to have focus, just make sure its tabindex attribute is set to anything other than -1. In regular HTML that would be by setting the tabindex attribute, but in React you have to use tabIndex (note the capital I).

You can also do it via JavaScript with element.setAttribute('tabindex',0)

This is what I was using it for, to make a custom DropDown menu.

var DropDownMenu = React.createClass({     getInitialState: function(){         return {             expanded: false         }     },     expand: function(){         this.setState({expanded: true});     },     collapse: function(){         this.setState({expanded: false});     },     render: function(){         if(this.state.expanded){             var dropdown = ...; //the dropdown content         } else {             var dropdown = undefined;         }                  return (             <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } >                 <div className="currentValue" onClick={this.expand}>                     {this.props.displayValue}                 </div>                 {dropdown}             </div>         );     } }); 
like image 41
Pablo Barría Urenda Avatar answered Sep 17 '22 02:09

Pablo Barría Urenda