detect click outside react component

  • Last Update :
  • Techknowledgy :

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. I had a similar use case where I had to develop a custom dropdown menu. it should close automatically when the user clicks outside. here is the recent React Hooks implementation- The click event contains properties like "path" which seems to hold the dom path that the event has traveled. 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?

This is our Splunktool team suggestion ✌, we tried and its working fine
const componentRef = useRef();​
useEffect(() => {
   document.addEventListener("click", handleClick);
   return () => document.removeEventListener("click", handleClick);

   function handleClick(e: any) {
      if (componentRef && componentRef.current) {
         const ref: any = componentRef.current
         if (!ref.contains(e.target)) {
            // put your action here
         }
      }
   }
}, []);
2._
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>;
}

After 16.3

import React, { Component } from "react";

/**
 * Component that alerts if you click outside of it
 */
export default class OutsideAlerter extends Component {
  constructor(props) {
    super(props);

    this.wrapperRef = React.createRef();
    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>;
  }
}

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

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

Suggestion : 2

The above code snippet assigns the current component instance’s DOM reference to the ref variable with the help of the useRef Hook. After that, it registers a click handler inside the useEffect Hook to the entire document to detect global click events. Look at the running application below. We can close the InfoBox instance by clicking outside of it. Moreover, it won’t disappear when you click on either button or the component. In these kinds of scenarios, if the user clicks outside a specific component, we have to trigger some actions.

First, we’ll create a new React app to get started. You can alternatively add the following outside click detection code to your existing React app.

Enter the following command and create a new app.

npx create - react - app react - outside - click
cd react - outside - click
yarn start

Now, we need to create a new functional component to implement the tooltip component. Add the following code into ./src/components/InfoBoxFunctional.js.

import { useEffect, useRef } from 'react';

export function InfoBox(props) {
  const ref = useRef(null);
  const { onClickOutside } = props;

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        onClickOutside && onClickOutside();
      }
    };
    document.addEventListener('click', handleClickOutside, true);
    return () => {
      document.removeEventListener('click', handleClickOutside, true);
    };
  }, [ onClickOutside ]);

  if(!props.show)
    return null;

  return (
    <div ref={ref} className='info-box'>
        {props.message}
    </div> );
}

The above code checks whether the user clicks on the tooltip (or its children) via the contains DOM API function. Hence, the onClickOutside callback will be executed if a click event occurs outside of the tooltip component instance.

The InfoBox component is ready now. Add the following CSS code to the ./src/index.css file to apply some styles for the InfoBox component. You can also move your InfoBox-related CSS into a separate file, if you like. We’ll use the index.css file for demonstration purposes.

body {
   margin: 0;
   font - family: -apple - system,
   BlinkMacSystemFont,
   'Segoe UI',
   'Roboto',
   'Oxygen',
   'Ubuntu',
   'Cantarell',
   'Fira Sans',
   'Droid Sans',
   'Helvetica Neue',
   sans - serif; -
   webkit - font - smoothing: antialiased; -
   moz - osx - font - smoothing: grayscale;
}
.container {
   display: flex;
   justify - content: center;
   padding - top: 40 vh;
}
.container.info - box - wrapper {
      position: relative;
   }
   .container.info - box {
      user - select: none;
      width: 300 px;
      background: #ffc00d;
      font - size: 14 px;
      padding: 12 px;
      box - shadow: 2 px 2 px 12 px rgba(0, 0, 0, 0.2);
      border - radius: 4 px;
      top: 20 px;
      position: absolute;
   }

The class-based component approach looks very similar to the functional component. We use the same props, DOM APIs, and implementation logic, but we have to write our code in the class-based style. Add the following code to ./src/components/InfoBoxClassBased.js.

import React from 'react';

export class InfoBox extends React.Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  handleClickOutside(event) {
    if (this.ref.current && !this.ref.current.contains(event.target)) {
      this.props.onClickOutside && this.props.onClickOutside();
    }
  };

  componentDidMount() {
    document.addEventListener('click', this.handleClickOutside, true);
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.handleClickOutside, true);
  };

  render() {
    if(!this.props.show)
      return null;
    return (
      <div ref={this.ref} className='info-box'>
        {this.props.message}
      </div> );
  }
}

As I mentioned before, you can easily add this outside click detection code to any of your React components. The implementation consists of a few DOM API function calls and React API usages. But, nowadays, we have npm libraries for literally anything we can think to do with React — including several libraries for this scenario. If you need to add this outside click detection logic into many components, and you don’t want to implement it yourself,  you can use a library. The react-outside-click-handler is a rather popular library for handling outside click events. Like any other npm library, this also affects your production bundle size a bit. React Outside Click Handler increases your production bundle size by about 20 kB. Let’s update our project with this npm library. We are going to modify both functional and class-based components by adding references to this npm library. Copy your current project into another directory and rename it to react-outside-click-lib. Install the npm library with the following command.

yarn add react - outside - click - handler

Suggestion : 3

A lightweight React hook that detects clicks outside elements and triggers a callback. Can also detect keypresses. 🖱 💻  Detects clicks outside an element and/or keypresses. Congrats! Your useDetectClickOutside should now trigger anytime a user clicks outside your component or presses the Escape key. A callback function, e.g. one that toggles the visibility of the component. By default, this function is triggered by a click outside the component, and by an Escape keyup event.

1._
yarn add react - detect - click - outside
2._
npm i react - detect - click - outside
3._
import {
   useDetectClickOutside
} from 'react-detect-click-outside';
5._
const ref = useDetectClickOutside({});
6._
const Container = () => {
    const [displayDropdown, setDisplayDropdown] = useState(false);

    const closeDropdown = () => {
        setDisplayDropdown(false);
    }
    return (
        { displayDropdown && <Dropdown/> }
    )
}

Suggestion : 4

We can use the createRef()  method to create a reference for any element in the class-based component. Then we can check whether click event occurred in the component or outside the component. In the functional component, we can use the useRef() hook to create a reference for any element. Step to Run Application: Run the application using the following command from the root directory of the project: Differences between Functional Components and Class Components in React

Creating React Application And Installing Module:

Step 1: Create a React application using the following command:

npx create - react - app foldername

Step 2: After creating your project folder i.e. foldername, move to it using the following command:

cd foldername

App.js: Now write down the following code in the App.js file. Here, App is our default component where we have written our code.

Filename- App.js: Using Class base Component

import React from 'react';
class App extends React.Component {
 
  constructor(props) {
    super(props);
 
    // Creating a reference
    this.box = React.createRef();
  }
 
  componentDidMount() {
 
    // Adding a click event listener
    document.addEventListener('click', this.handleOutsideClick);
  }
 
  handleOutsideClick = (event) => {
    if (this.box && !this.box.current.contains(event.target)) {
      alert('you just clicked outside of box!');
    }
  }
 
  render() {
    return <div style={{
      margin: 300,
      width: 200, height: 200, backgroundColor: 'green'
    }}
 
      // Assigning the ref to div component
      ref={this.box}>{this.props.children}</div>;
  }
}
 
export default App;
5._
npm start

Suggestion : 5

A tutorial about how to detect a click outside of a React component by creating a custom React hook for it. For example, you may want such custom React hook for various components like a dialog or dropdown, because they should close when a user clicks outside of them. So we need a way to find out about this outside click. A neat custom React Hook that I used in some of my React freelance projects which checks if an element's content has overflow (here: vertical overflow): If you want to detect a horizontal… That's it. You created a custom hook which detects clicks outside of referenced components/elements. Again, read through the event bubbling and capturing article to get a more in-depth explanation of what's going on in these phases.

Let's kick things off with a in React where we increment a counter by using and an event handler:

import * as React from 'react';
const style = {  padding: '10px',  border: '1px solid black',  display: 'flex',  justifyContent: 'flex-end',};
function App() {  const [count, setCount] = React.useState(0);
  const handleClick = () => {    setCount((state) => state + 1);  };
  return (    <div style={style}>      <button type="button" onClick={handleClick}>        Count: {count}      </button>    </div>  );}
export default App;

Everything works as expected. Next we want to reset the state (here: count) whenever a user clicks outside of the button. We can write the for resetting the state, however, it's not clear yet where to use it:

function App() {  const [count, setCount] = React.useState(0);
  const handleClickOutside = () => {    setCount(0);  };
  const handleClick = () => {    setCount((state) => state + 1);  };
  return (    <div style={style}>      <button type="button" onClick={handleClick}>        Count: {count}      </button>    </div>  );}

A naive approach would be using this new handler on the outermost HTML element of the top-level component (here: <div>). However, a better approach would be using this event handler on a document level as a best practice, because the outermost HTML element can change during the development process.

We will implement this in a custom hook straightaway to avoid a redundant refactoring:

const useOutsideClick = (callback) => {
   const ref = React.useRef();
   React.useEffect(() => {
      const handleClick = (event) => {
         callback();
      };
      document.addEventListener('click', handleClick);
      return () => {
         document.removeEventListener('click', handleClick);
      };
   }, []);
   return ref;
};

However, as you will notice, the handler will always fire, also when the button itself gets clicked. If you check the custom hook again, you will see that the reference (read: ref) is not really used in there. What we want to accomplish: Execute the callback function only when anything outside of the passed ref (representing the button here) is clicked, not when the ref itself (or its content) gets clicked:

const useOutsideClick = (callback) => {
   const ref = React.useRef();
   React.useEffect(() => {
      const handleClick = (event) => {
         if (ref.current && !ref.current.contains(event.target)) {
            callback();
         }
      };
      document.addEventListener('click', handleClick);
      return () => {
         document.removeEventListener('click', handleClick);
      };
   }, [ref]);
   return ref;
};

That's it. The reference assigned to the button is the border between triggering the button's event handler and the document's event handler. Everything clicked that's outside of the reference will be considered as an outside click.

There is a small improvement missing though: What if we need to stop the event bubbling for by using the stopPropagation() method on an event handler. For example, in the following we extend the component with a click on the container element and stop the propagation of the event there:

const style = {  padding: '10px',  border: '1px solid black',  display: 'flex',  justifyContent: 'space-between',};
...
function App() {  const [count, setCount] = React.useState(0);
  const handleClickOutside = () => {    setCount(0);  };
  const ref = useOutsideClick(handleClickOutside);
  const handleClick = () => {    setCount((state) => state + 1);  };
  const handleHeaderClick = (event) => {    // do something
    event.stopPropagation();  };
  return (    <div style={style} onClick={handleHeaderClick}>      <div>Header</div>      <button ref={ref} type="button" onClick={handleClick}>        Count: {count}      </button>    </div>  );}

Suggestion : 6

Selected the HTML element with the class click-text. Put a mouse down event listener on document and set an event handler callback function. Let’s quickly test it out. Let’s make an element we want to detect outside click for. I’ve conveniently given it a click-text class. This should work in the same way as the outside click detection had worked before. Let’s try clicking outside of the grey text element in the codesandbox below, and observe the console:

1._
<section>
   <div class="click-text">
      click inside and outside me
   </div>
</section>
2._
const concernedElement = document.querySelector(".click-text");

document.addEventListener("mousedown", (event) => {
   if (concernedElement.contains(event.target)) {
      console.log("Clicked Inside");
   } else {
      console.log("Clicked Outside / Elsewhere");
   }
});
3._
<OutsideClickHandler
  onOutsideClick={() => {
    console.log("I am called whenever click happens outside of 'AnyOtherReactComponent' component")
  }}
>
  <AnyOtherReactComponent />
</OutsideClickHandler>

Just a basic React component. So far, we are not doing much with it. We’re just returning the children as they are passed to our OutsideClickHandler component. Let’s wrap the children with a div element and attach a React ref to it.

import React, { createRef } from 'react';

class OutsideClickHandler extends React.Component {
  wrapperRef = createRef();

  render() {    
    return (
      <div ref={this.wrapperRef}>
        {this.props.children}
      </div>
    )
  }  
}
6._
class OutsideClickHandler extends React.Component {
   componentDidMount() {
      document
         .addEventListener('mousedown', this.handleClickOutside);
   }

   componentWillUnmount() {
      document
         .removeEventListener('mousedown', this.handleClickOutside);
   }

   handleClickOutside = (event) => {
      // Here, we'll write the same outside click
      // detection logic as we used before.
   }
}

Suggestion : 7

The ClickAwayListener component detects when a click event happens outside of its child element. By default <ClickAwayListener /> will add an onClick handler to its children. This can result in e.g. screen readers announcing the children as clickable. However, the purpose of the onClick handler is not to make children interactive. Notice that the component only accepts one child element. You can find a more advanced demo on the Menu documentation section. In order to prevent screen readers from marking non-interactive children as "clickable" add role="presentation" to the immediate children:

1._
<ClickAwayListener onClickAway={handleClickAway}>
  <Box sx={{ position: 'relative' }}>
    <button type="button" onClick={handleClick}>
      Open menu dropdown
    </button>
    {open ? (
      <Box sx={styles}>
        Click me, I will stay visible until you click outside.
      </Box>
    ) : null}
  </Box>
</ClickAwayListener>
2._
<ClickAwayListener onClickAway={handleClickAway}>
  <div>
    <button type="button" onClick={handleClick}>
      Open menu dropdown
    </button>
    {open ? (
      <Portal>
        <Box sx={styles}>
          Click me, I will stay visible until you click outside.
        </Box>
      </Portal>
    ) : null}
  </div>
</ClickAwayListener>
3._
<ClickAwayListener
  mouseEvent="onMouseDown"
  touchEvent="onTouchStart"
  onClickAway={handleClickAway}
>
  <Box sx={{ position: 'relative' }}>
    <button type="button" onClick={handleClick}>
      Open menu dropdown
    </button>
    {open ? (
      <Box sx={styles}>
        Click me, I will stay visible until you click outside.
      </Box>
    ) : null}
  </Box>
</ClickAwayListener>
5._
import ClickAwayListener from '@mui/base/ClickAwayListener';

Suggestion : 8

Using the contains API, we can identify whether a target node (the component on which the user has clicked) is inside a particular node or not. That is, if the clicked component is within (or itself) the component we are interested in, then it will return true otherwise false. You might have come across instances where you would want to do certain actions when the user clicks outside a component, say like closing a modal or a dropdown menu. In this tutorial, we will display a dropdown and close the dropdown when the user clicks outside it. Also, we are running an effect whenever the state of the menu changes and we are binding a mousedown event to the document so that whenever the user clicks on the document, we can check if it is inside or outside the wrapper and hide the list accordingly.

1._
1n px create - react - app react - on - click - outside
2._
1 body {
   2 margin: 0 auto;
   3 max - width: 500 px;
   4
}
5. wrapper {
   6 display: inline - flex;
   7 flex - direction: column;
   8
}
9
10. button {
   11 margin: 20 px 0 px 0 px 0 px;
   12 border: 1 px solid #2185d0;13  padding: 10px;14  border-radius: 5px;15  cursor: pointer;16  font-weight: bold;17  background-color: white;18  width: 140px;19}20
21.list {22  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);23  border: 1px solid # ccc;
   24 list - style - type: none;
   25 padding: 0;
   26 margin: 0;
   27 width: auto;
   28 display: inline - block;
   29
}
30. list - item {
   31 padding: 8 px;
   32 cursor: pointer;
   33 background - color: white;
   34
}
35. list - item: hover, 36. list - item: active {
   37 background - color: #f3f3f3;
   38
}
3._
1import { useState } from "react"2
3function App() {4  const [isMenuOpen, setIsMenuOpen] = useState(false)5
6  return (7    <div className="wrapper">8      <button9        className="button"10        onClick={() => setIsMenuOpen(oldState => !oldState)}11      >12        Click Me13      </button>14      {isMenuOpen && (15        <ul className="list">16          <li className="list-item">dropdown option 1</li>17          <li className="list-item">dropdown option 2</li>18          <li className="list-item">dropdown option 3</li>19          <li className="list-item">dropdown option 4</li>20        </ul>21      )}22    </div>23  )24}25
26export default App