Paul Ho
4 min readNov 3, 2020

--

clickOutside using useRef hooks

Firstly, congratulations on the release of React v17.0

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

One of the hooks is useRef. We will look into the application of useRef, on how to detect a click outside in React.

Let’s create a new React application

npx create-react-app react-clickoutside

Styling with Tailwind CSS

Follow this guide installing Tailwind CSS in React.

Let’s create a simple dropdown menu

Now let’s create simple dropdown menu, basically a toggle button.

Replace App.js with following codes.

import React from 'react';
import './styles/main.css';
import Layout from './components/Layout'
const App = () => {
return (
<Layout>
<h1 className="text-3xl text-black pb-6">Outside</h1
</Layout>
);
}
export default App;

As you can see now, we need Layout.js, let’s create subfolder components in src directory. Add Layout.js with following codes. I am assuming you familiar with Tailwind CSS styling.

import React from 'react';
import Header from '../components/Header'
const Layout = ({children}) => {
return (
<div className="bg-gray-100 font-family-karla flex">
<div className="relative w-full flex flex-col h-screen overflow-y-hidden">
<Header />
<div className="w-full h-screen overflow-x-hidden border-t flex flex-col">
<main className="w-full flex-grow p-6">
{children}
</main>
</div>
</div>
</div>
);
}
export default Layout;

And, we need to add Header.js which contains the dropdown menu. Don’t forget add in image file sign-in.png into the same folder.

import React, {useState, useRef} from "react";
import myImg from './sign-in.png';
const Header = () => {
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
if (!isOpen) {
setIsOpen(true)
}
if (isOpen) {
setIsOpen(false);
}
}
return (
<header className="w-full flex items-center bg-white py-2 px-6">
<div className="w-1/2"></div>
<div className="relative w-1/2 flex justify-end">
<button onClick ={handleClick} className="realtive z-10 w-12 h-12 rounded-full overflow-hidden border-4 border-gray-400 hover:border-gray-300 focus:border-gray-300 focus:outline-none">
<img src={myImg} alt="sign-in" />
</button>
{isOpen && (
<div className="absolute w-32 bg-white rounded-lg shadow-lg py-2 mt-16">
<a href="#" className="block px-4 py-2">Sign In</a>
</div>
)}
</div>
</header>
);
}
export default Header;

Now start the application by executing command npm start or yarn start.

Now you can open dropdown menu by clicking the button and close it only by clicking the button.

Close the dropdown menu by click outside

Come back to the purpose of this article, is to demonstrate the application of useRef hooks. To detect the click outside, we use useRef hooks in Header.js

import React, {useState, useRef} from "react";
import myImg from './sign-in.png';
import useClickOutside from "../lib/clickOutside";
const Header = () => {
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
if (!isOpen) {
setIsOpen(true)
}
if (isOpen) {
setIsOpen(false);
}
}
const pullDown = useRef();
useClickOutside(() => setIsOpen(false), pullDown);
.......

We will create subfolder lib in src directory. Add file clickOutside.js with following codes

import { useEffect } from "react";const useClickOutside = ( closeModal, ref ) => {
const handleClickOutside = (e) => {
if (!ref || !ref.current.contains(e.target)) {
closeModal();
}
};
useEffect(() => {
// add when mounted
document.addEventListener("click", handleClickOutside, true);
// return function to be called when unmounted
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
};
export default useClickOutside;

clickOutside.js has ‘click’ event listener that handle click outside by receiving callback function closeModal and ref hooks from its parent.

Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

Let’s finish up Header.js by adding ref in <button> tag.

<button onClick ={handleClick} ref={pullDown} className="realtive z-10 w-12 h-12 rounded-full overflow-hidden border-4 border-gray-400 hover:border-gray-300 focus:border-gray-300 focus:outline-none">
<img src={myImg} alt="sign-in" />
</button>

the final codes in Header.js

import React, {useState, useRef} from "react";
import myImg from './sign-in.png';
import useClickOutside from "../lib/clickOutside";
const Header = () => {
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
if (!isOpen) {
setIsOpen(true)
}
if (isOpen) {
setIsOpen(false);
}
}
const pullDown = useRef();
useClickOutside(() => setIsOpen(false), pullDown);
return (
<header className="w-full flex items-center bg-white py-2 px-6">
<div className="w-1/2"></div>
<div className="relative w-1/2 flex justify-end">
<button onClick ={handleClick} className="realtive z-10 w-12 h-12 rounded-full overflow-hidden border-4 border-gray-400 hover:border-gray-300 focus:border-gray-300 focus:outline-none">
<img src={myImg} alt="sign-in" />
</button>
{isOpen && (
<div className="absolute w-32 bg-white rounded-lg shadow-lg py-2 mt-16">
<a href="#" className="block px-4 py-2">Sign In</a>
</div>
)}
</div>
</header>
);
}
export default Header;

Start the app again.

click outside works! You can find the code in github repo

See you in next post.

--

--