As a web developer, you may come across situations where you need to detect when a user clicks outside of a specific element, such as a modal or dropdown menu. This can be useful for closing the element or triggering some other action when the user clicks outside of it.
In React, you can detect a click outside of a component by using a technique called “event bubbling” or “event capturing”. This involves attaching an event listener to the document object and checking if the click target is outside of the component.
In this blog post, we’ll understand how to detect a click outside of a React component using the `useRef` and `useEffect` hooks in a step-by-step fashion using an example problem as it is better to understand things using practical examples.
For this, we will be making a basic counter app that will increase its count by 1 when clicked inside and will decrease its count by 1 when clicked outside.
Let’s start, but first to create a React app to understand this concept.
Index
Creating the React App
First and foremost we have to create our React app. It’s straightforward to create a React app – just go to your working directory in your preferred IDE and enter the following command in the terminal:
npx create-react-app detecting-a-click-outside
If you are unsure how to set up a create-react-app project properly, you can refer to the official guide here at create-react-app-dev.‌‌
After the setup, run npm start in the same terminal to start localhost:3000 where our React app will be hosted. We can also see all our changes there.
What are we making?
In this app, we will be making a counter app that contains a button wrapped up by a div. The initial value of the counter is 0. With each button click, the value of the counter gets increased by 1, while will decrease if anything other than the button and the wrapper element div is clicked.
Working on the App
Let’s start by making the counter which gets increased by 1 every time the user clicks it. In the below code, we have a wrapper element div wrapping a button that contains the counter with an initial value of 0.
With the help of the useState hook, we have initialized a state for this counter which will track the state change and will update the counter, and a function will be responsible for this updation of the state with the logic written inside it which increases the value of the counter by 1 whenever the user clicks the button.
This is because of the onClick() event handler that we used on the button element to trigger the handleClick() function whenever the user clicks the button resulting in an increase of the counter by 1.
import React, { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<div className="counter">
<button onClick={handleClick}>
Count:
<span style={{ color: "red" }}>{count}</span>
</button>
</div>
</>
);
};
export default App;
In the above gif, we can clearly see that the counter is getting increased by 1 whenever the button gets clicked. But what if we somehow want its value to get decreased by 1 whenever the user clicks outside of the div and the button element?
Detecting the Outside Click of a functional component
As told in the introduction part of the blog, we can very easily achieve this with the help of useRef and useEffect hook. Let’s understand this in a step-by-step fashion.
Use a React Ref to Create a Clickable Area
We will be using the useRef hook to create a reference of the component’s root element thus creating a clickable area.
import React, { useState, useRef } from "react";
const App = () => {
const [count, setCount] = useState(0);
const newRef = useRef(null);
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<div className="counter" ref={newRef}>
<button onClick={handleClick}>
Count :
<span style={{ color: "red" }}>{count}</span>
</button>
</div>
</>
);
};
export default App;
Creating an event listener for the clickable area
Alone useRef hook cannot achieve this functionality. Thus, we will be using the useEffect hook for adding and removing event listeners in conjunction with the mousedown event listener.
The reference we have just made of the DOM element will register a click handler inside the useEffect Hook as told which will detect a global click event. This is where the component gets mounted.
When the component is unmounted, we can unregister our global click event listener, because the useEffect Hook also allows us to return a function with cleanup code. As we can see, the removeEventListener DOM API function is used to implement cleanup code in the code below-mentioned.
import React, { useState, useRef, useEffect } from "react";
const App = () => {
const [count, setCount] = useState(0);
const newRef = useRef(null);
useEffect(() => {
document.addEventListener("mousedown", handleOutsideClick);
return () => {
document.removeEventListener("mousedown", handleOutsideClick);
};
});
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<div className="counter" ref={newRef}>
<button onClick={handleClick}>
Count :
<span style={{ color: "red" }}>{count}</span>
</button>
</div>
</>
);
};
export default App;
Checking if the click is outside of the React component
To check this, we will be using the reference of the root element that we have made in the latter section with the help of the useRef hook (as we also see in the above code) and the current property of the useRef hook which lets us get a sneak peek on the current value of the ref.
By default, its value is true, which means it will trigger the handleOutsideClick() function whenever the user clicks inside the div component and we don’t want that to happen. What we want is the complete opposite of it, thus we will be using the logical NOT Operator before this
const handleOutsideClick = (e) => {
if (newRef.current && !newRef.current.contains(e.target)) {
setCount(count - 1);
}
};
logic inside the if statement.
What this will do is trigger the handleOutsideClick() function whenever the user clicks outside of the root element – in this case, the div element. Now that we have all the pieces in our hands, it’s time to use them in conjunction.
With the help of the useEffect hook, we can add an event listener to the document object for the ‘mousedown’ event. Inside the event handler, we check if the clicked element is outside the component by using the current method of the component’s root element as shown above.
Finally, we remove the event listener when the component unmounts by returning a cleanup function from the useEffect hook giving us the functionality of decreasing the value of the counter by 1 when a click is detected out of the root element.
This approach assumes that the root component is not nested inside another component. If that’s the case, you’ll need to modify the event handler to check if the clicked element is outside of both components.
Different Ways using which we can Detect a Click Outside of a React Component
In this blog, we have seen one way using which we can detect whether the click has been made inside or outside of the root component.
In the method we have used two separate functions which gets triggered when
We have achieved this functionality with the help of an event listener, the useRef hook, and the useEffect hook. But we can also use the npm package which is specifically made for this purpose. Given below are some of them
- react-click-outside: This package provides a higher-order component that can be used to wrap a React component and detect clicks outside of it. When a click is detected outside of the component, a callback function is called.
- react-onclickoutside: This package provides a mixin that can be used to attach a click event listener to the document object and detect clicks outside of a React component. When a click is detected outside of the component, a callback function is called.
- react-outside-click-handler: This package provides a component that can be used to detect clicks outside of a React component. When a click is detected outside of the component, a callback function is called.
- use-onclickoutside: This package provides a custom hook that can be used to attach a click event listener to a DOM element and detect clicks outside of a React component. When a click is detected outside of the component, a callback function is called.
- use-click-away: This package provides a custom hook that can be used to detect clicks outside of a React component. When a click is detected outside of the component, a callback function is called.
Conclusion
In this article, we understood how can we detect a click outside of a React Component using the useRef and useEffect hook of React and their different properties like the clean-up function of useEffect hook and the current property of useRef.
The process is very simple, just make a reference of the root element and get its current value using the current property of the useRef hook to check whether the click is detected outside or inside. And then with the help of the useEffect hook, add and remove the event listener as the component mounts and unmounts using the mousedown event listener.
If you want to understand more about this concept in detail, we would recommend you to tread our blog on event capturing and bubbling.
Frequently Asked Question
How can I detect a click outside of a React component using hooks?
To detect a click outside using hooks, you can utilize the useRef
and useEffect
hooks. By creating a ref for the component and attaching event listeners to the mousedown
events, you can check if the clicked element is outside the component and handle the click outside behavior accordingly.
How can I close a dropdown or menu when a user clicks outside of it in React?
To close a dropdown or menu when a user clicks outside, you can use the click outside detection technique. When the user clicks outside the dropdown component, you can update the state or trigger a function that closes the dropdown.
Can I detect a click outside of a React component without using event listeners?
Yes, you can use the useRef
hook along with the useEffect
hook to detect clicks outside of a component without using event listeners. By checking if the clicked element is outside the component using the ref
and the event.target
, you can handle the click-outside behavior.
How can I prevent a click inside a specific element from triggering the click outside behavior?
To prevent a click inside a specific element, such as a button or input field, from triggering the click outside behavior, you can use the event.stopPropagation()
method within the event handler for that specific element. This prevents the click event from bubbling up and triggering the click outside logic.
Is there a way to detect clicks outside of a React component on mobile devices with touch screens?
Yes, you can adapt the click-outside detection to work on mobile devices with touch screens. Instead of listening for mousedown
events, you can listen for touchstart
or touchend
events and handle the touch outside logic similar to the click outside logic.