Detect click outside React component Detect click outside React component reactjs reactjs

Detect click outside React component


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:

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,};

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>;}


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>        );    }});


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.