Easily Detect Outside Click Using useRef Hook

Full-Stack Developer, Entrepreneur and Co-Founder of Coderplex.
Building https://coderplex.in
Checkout my portfolio at https://bhanuteja.dev
Search for a command to run...

Full-Stack Developer, Entrepreneur and Co-Founder of Coderplex.
Building https://coderplex.in
Checkout my portfolio at https://bhanuteja.dev
No comments yet. Be the first to comment.
In this series, I will write different blog posts based on workshop content from [Kent C. Dodds](https://kentcdodds.com)'s [EpicReact.Dev](https://epicreact.dev) as I go through it.
In this article, we will see the order in which different useEffect callbacks and cleanups happen. We will also see how it differs when the app mounts, unmounts, updates. This image is taken from https://github.com/donavon/hook-flow. I took the ex...
Authored in connection with the Write With Fauna program. Table of Contents Authentication Setting up Fauna in Next.js Installing Fauna Setting up Migrations Tool Authentication and Authorization in Fauna Next.js Serverless Function Setup for Fa...

I wanted to start blogging in Aug of 2020. I decided to use @hashnode for my blog. It was the best decision that I made. I wrote 27 technical articles so far. Most of them are about frontend web development. A thread 🧵 about each of these articles i...

Next.js has become my go-to framework for almost every project that I make. So, I made a starter template that I can just use and get started easily. In this article, I will show you how to use the starter template that I made and deploy it with Verc...

In this article, I will list out all the git commands that I use very frequently. This is not in any way a complete list, just the commands that I use very often. This is intended to be used as a quick reference to perform an action that you want. ...

In this article, we will see the order in which different useEffect callbacks and cleanups happen. We will also see how it differs when the app mounts, unmounts, updates. This image is taken from https://github.com/donavon/hook-flow. I took the ex...

Bhanu Teja Pachipulusu's blog
29 posts
Developer, Indie Maker and Blogger. Currently building MDX.one
Checkout my portfolio
Hooks are special types of functions in React that you can call inside React functional components. They let you store data, add interactivity, and perform some actions, otherwise known as side-effects.
The most common hooks are:
In the previous article (How to Create a Reusable LocalStorage Hook), we learned about useEffect hook and how we can use it to create a custom and reusable hook that persists the state by storing it in local storage. If you haven't read that article, please go and read it before going through this article. We will be using useEffect in this article.
This is a special inbuilt function in React that gives you a direct reference to DOM node. Usually, in React, you won't have access to the DOM nodes directly. But sometimes, you may want to get access to DOM nodes directly because of various reasons, like the library that you use may need that.
useRef takes a single argument which is the initial value for the ref and creates and returns a ref.
const elementRef = useRef(null)
Now, the way to ask React to give you the access to DOM node is to assign the created ref to the ref prop of the element in JSX.
For example,
function HelloWorld() {
// create the ref
const elementRef = useRef(null)
return (
{ /* Asking React for the access to the DOM node */ }
<>
<div ref={elementRef}>
Hello World
</div>
</>
)
}
Now, when you add the ref prop for the JSX element, React understands that you want direct reference to the DOM node of that element, and then it sets the current property of that elementRef to the DOM node.
In the above example, you can access the DOM node by using elementRef.current
Let's use this to detect whenever you click outside of an element.
Some of the practical use-cases where you may want to detect if you clicked outside of an element are:
function App() {
const [isOpen, setIsOpen] = useState(true)
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<div id="modal">
<Modal isOpen={isOpen}>
This is the modal dialog
</Modal>
</div>
</>
)
}
Let's take this simple component. It has a heading, a button which when clicked opens the modal.
Our goal is to detect and execute setIsOpen(false) whenever we click outside of div with id modal.
Let's see how we can achieve this.
modal.setIsOpen(false)We can use useRef for this.
function App() {
const [isOpen, setIsOpen] = useState(true)
// change starts here
const modalRef = useRef()
// change ends here
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<!-- Change starts here -->
<div id="modal" ref={modalRef}>
<!-- Change ends here -->
<Modal isOpen={isOpen}>
This is the modal dialog
</Modal>
</div>
</>
)
}
Now, after the app gets rendered, modalRef.current will have access to the required DOM node.
We can add an event listener inside useEffect.
useEffect(() => {
function handler(event) {
console.log(event, 'clicked somewhere')
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
Here we added a click event listener to the entire window to detect the click anywhere on the window.
We can know where the click happened based on event.target. We just have to check if our modal div contains event.target or not.
useEffect(() => {
function handler(event) {
// change starts here
if(!modalRef.current?.contains(event.target)) {
console.log('clicked outside of modal')
}
// change starts here
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
This step is straight-forward. We just have to execute setIsOpen(false) whenever we detect the click outside the modal.
useEffect(() => {
function handler(event) {
if(!modalRef.current?.contains(event.target)) {
// change starts here
setIsOpen(false)
// change starts here
}
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
Let's put everything together.
function App() {
const [isOpen, setIsOpen] = useState(true)
const modalRef = useRef()
useEffect(() => {
function handler(event) {
if(!modalRef.current?.contains(event.target)) {
setIsOpen(false)
}
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<div id="modal" ref={modalRef}>
<Modal isOpen={isOpen}>
This is the modal dialog
</Modal>
</div>
</>
)
}
We can create a reusable hook out of this that you can use anywhere.
import { useEffect, useRef } from 'react'
export default function useOnClickOutsideRef(callback, initialValue = null) {
const elementRef = useRef(initialValue)
useEffect(() => {
function handler(event) {
if (!elementRef.current?.contains(event.target)) {
callback()
}
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [callback])
return elementRef
}
In this hook, we are creating a ref and then returning it at the end. This way, the API looks kinda similar to how you create a ref using useRef. But the ref created using this custom hook has the additional functionality to detect and execute a callback whenever a click is detected outside.
Let's change our example to use this hook.
function App() {
const [isOpen, setIsOpen] = useState(true)
const modalRef = useOnClickOutsideRef(() => setIsOpen(false))
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<div id="modal" ref={modalRef}>
<Modal isOpen={isOpen}>
This is the modal dialog
</Modal>
</div>
</>
)
}
That's it. You now have the exact same functionality as you have before. The only thing you changed here is changing useRef() to useOnClickOutsideRef(() => setIsOpen(false)).
Accessing DOM nodes is not the only case when you can use ref. You can use ref to keep a reference to any value. You can even mutate the ref directly using exampleRef.current = 'something'. Mutating the ref will not cause the component to re-render. So, whenever you want to keep track of a value and want to mutate it without causing the component to re-render, you can make use of useRef hook.
To know a practical use-case which uses useRef to keep track of a value, checkout this excellent article by Tapas Adhikary - How to use JavaScript scheduling methods with React hooks
ref (created using useRef hook) to the ref property of JSX element, React automatically sets the current property of that ref to the DOM node of the corresponsing element.ref.current property directly and mutating it does not cause the component to re-render.useOnClickOutsideRef using useRef and useEffect - which can detect and execute a callback whenever you clicked outside of an element.In the next article, we will look at the hooks flow to see in which order different hooks will get executed. We will also see what lifting state and colocating state mean and when to use each of them.
If you liked this article, check out
You can also follow me on Twitter at @pbteja1998.