Skip to content

Detecting Clicks Outside of a Component in React

React/

It can be very useful to detect clicks outside of a specific component in your React application. For example, when you have a modal or a dropdown component, you might want to close them automatically when user clicks somewhere else on the page.

In this article, you will learn how detect if user has clicked outside of a component in React. Furthermore, you will see how to:

Detecting Clicks Outside of a Component

To demonstrate how to detect clicks outside of a component in React, I will use a basic modal component.

You can see the complete code here on CodeSandBox, otherwise let’s create it step by step.

Here’s an application that conditionally renders a modal:

export default function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <p>Page Content</p>
      <button onClick={(event) => setShowModal(true)}>Open modal</button>

      {showModal && <Modal onClose={() => setShowModal(false)} />}
    </div>
  );
}
  • State variable showModal controls whether a modal is shown or not
  • Initially state is set to false so the Modal component is not rendered
  • If the button is pressed, state is set to true and the Modal is rendered
  • The Modal accepts a callback function via its onClose prop
  • When the Modal should be closed, you can call the onClose callback function from the Modal and hide it

And here is the Modal component itself:

export default function Modal({ onClose }) {
  return (
    <div>
      <p>Modal content</p>
      <button onClick={onClose}>Close modal</button>
    </div>
  );
}

For now, it can be closed only by pressing the close button.

Now, let’s add code to close the modal when clicking outside of it.

First, you need to listen to any mouse click. For that, you can add an event listener to the global document object.

import { useEffect } from "react";

export default function Modal({ onClose }: { onClose: () => void }) {
  useEffect(() => {
    const handleClick = (event: MouseEvent) => {
      // Handle the click event here
    };

    document.addEventListener("click", handleClick, true);

    return () => {
      document.removeEventListener("click", handleClick, true);
    };
  }, [onClose]);

  return (
    <div>
      <p>Modal content</p>
      <button onClick={onClose}>Close modal</button>
    </div>
  );
}
  • You can use useEffect hook to register and deregister event listeners on the document object
  • It is important to remove the event listener, otherwise document will keep listening to click events event after Modal is closed and not rendered

Before you can handle the click event, you will need a reference to the modal DOM element. You need it so that you can tell if you have clicked on the modal itself. If that’s not the case, you have clicked outside.

import { useEffect, useRef } from "react";

export default function Modal({ onClose }: { onClose: () => void }) {
  const modalRef = useRef<HTMLDivElement>(null);

  // ✂️ useEffect(() => { /* ... */ });

  return (
    <div ref={modalRef}>
      <p>Modal content</p>
      <button onClick={onClose}>Close modal</button>
    </div>
  );
}

With those things in place, you can check if the click has targeted your modal. If it has, you ignore the click, otherwise the user has clicked outside of the modal and you can close it.

import { useEffect, useRef } from "react";

export default function Modal({ onClose }: { onClose: () => void }) {
  const modalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleClick = (event: MouseEvent) => {
      if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
        onClose();
      }
    };

    document.addEventListener("click", handleClick, true);

    return () => {
      document.removeEventListener("click", handleClick, true);
    };
  }, [modalRef, onClose]);

  return (
    <div ref={modalRef}>
      <p>Modal content</p>
      <button onClick={onClose}>Close modal</button>
    </div>
  );
}

Here’s what’s happening:

  • You create a reference that will hold the entire DOM node of your modal, including its content
  • You register an event listener that reacts to user clicks by calling handleClick
  • With handleClick, you:
    • Check if modalRef does not contain its initial value of null
    • Check if the DOM node held by modalRef does not contain the same element that was clicked via event.target
  • You call the onClose function if event.target contains element that is not found in modalRef

A natural next step and big improvement you can do is to make this functionality reusable by moving it into a hook so that you can use it for other components.

Creating a Hook to Detect Clicks Outside Any Component

Now that you have seen how to automatically close a modal component when clicking outside of it, it is time to learn how to turn that functionality into a reusable hook. This way you can use it for any component you want, such as tooltips, dropdown menus or sidebars.

Here are the steps to create a reusable hook that detects clicks outside of a component in React.

  1. Create a file for your hook like src/hooks/useClickOutside.ts and add the following code

    import { useEffect, useRef } from "react";
    
    const useClickOutside = (callback: Function) => {
      const ref = useRef<any>(null);
    
      useEffect(() => {
        const handleClick = (event: MouseEvent) => {
          if (!ref.current?.contains(event.target as Node)) {
            callback();
          }
        };
    
        document.addEventListener("click", handleClick, true);
    
        return () => {
          document.removeEventListener("click", handleClick, true);
        };
      }, [ref, callback]);
    
      return ref;
    };
    
    export default useClickOutside;
    

    This hook creates a reference and returns it. You will have to attach that reference to the HTML element which you want to detect clicks outside of.

  2. Use the hook in your component by passing it a function you want to be called when a click outside of the referenced element is made

    import { useState } from "react";
    import useClickOutside from "../hooks/useClickOutside";
    
    export default function Dropdown() {
      const [isOpen, setIsOpen] = useState(false);
    
      function handleClickOutside() {
        setIsOpen(false);
      }
    
      const ref = useClickOutside(handleClickOutside);
    
      return (
        <div ref={ref} className="dropdown">
          <button onClick={() => setIsOpen((value) => !value)}>
            Open dropdown
          </button>
          {isOpen && (
            <ul>
              <li>Item 1</li>
              <li>Item 2</li>
              <li>Item 3</li>
            </ul>
          )}
        </div>
      );
    }
    

    In this example, a dropdown menu can be opened or closed by pressing a button. It is also closed when clicking outside of the menu (div element) with the help of useClickOutside hook that calls handleClickOutside function.

Now that you have created the hook, you can attach its returned reference to any element you wish.

Using an Existing Solution

In the previous section you wrote a custom hook to detect clicks outside of any component. If you want to save yourself the work, you can use a ready-made solution from react-use package.

Here are the steps how to detect clicks outside of a component using react-use.

  1. Install react-use library by running the install command from a command line from the root of your project

    npm install react-use
    
  2. Import and use useClickAway hook from react-use

    import { useRef, useState } from "react";
    import { useClickAway } from "react-use";
    
    export default function Sidebar() {
      const [isOpen, setIsOpen] = useState(false);
      const ref = useRef(null);
    
      function handleClickOutside() {
        setIsOpen(false);
      }
    
      useClickAway(ref, handleClickOutside);
    
      return (
        <>
          <button onClick={() => setIsOpen(true)}>Open sidebar</button>
          {isOpen && (
            <div ref={ref}>
              <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
              </ul>
            </div>
          )}
        </>
      );
    }
    

    You have to create a reference using useRef hook and pass it to useClickAway hook along with a function that you want to be called when a click outside of the referenced element is made.

Summary

In this article your learned how to detect clicks outside of a given component in React. You then extracted that functionality into its own custom hook, so that it can be reused for any component. You also saw how to achieve the same goal by using an existing solution from react-use.

If you want to see the complete code with examples of a modal, dropdown and a sidebar components, head to this CodeSandBox project.