Practical Bits - React Custom Hooks
Published: 2021-12-17 08:41:15

I’m starting a series where I write my experience using concepts. I’m doing this because most times we hear about new or special concepts with the tools we use but never get to use them or fully understand them. Mostly, I’m doing this to gain mastery of the tools I work with (HTML, CSS, React) so disclaimer, this is not expert opinion.

I came across a problem recently where I was going to copy/paste logic across different components. The components were a dropdown and an input element, I needed to be able to

- Close the dropdown when I clicked anywhere outside it and
- Hide the input field when I clicked anywhere outside it

So, the common denominator was “when I clicked outside the element”. There’s a package for this, look out for a link in the last section. I also found an easy solution and had it working for me on the dropdown element. To avoid repeating logic too many times, I thought about using a custom hook.

Snippet from the dropdown component

```javascript
 const selectRef = useRef(null);
 useEffect(() => {
   function handleClickOutside(event) {
     if (selectRef.current && !selectRef.current.contains(event.target)) {
       close();
     }
   }
   document.addEventListener("mousedown", handleClickOutside);
   return () => {
     document.removeEventListener("mousedown", handleClickOutside);
   };
 }, [selectRef]);
 
 return (<div ref={selectRef}></div>)
```
Essentially, I’m adding an event listener that listens to the mousedown event and carries out the handleClickOutside function. The function checks if the clicked element is the element with the specified ref and if not, it calls a function `close()` that closes the dropdown in the div.

A custom hook helps to extract reusable logic in to a function. Extracting the logic to a hook

```javascript
import { useState, useEffect } from "react";
 
export function useClickedOut(ref) {
const [active, setActive] = useState(false);
 
 const onClickOutside = (event) => {
   if (ref.current && !ref.current.contains(event.target)) {
     setActive(false);
   }
 };
 
 useEffect(() => {
   document.addEventListener("mousedown", onClickOutside);
   return () => {
     document.removeEventListener("mousedown", onClickOutside);
   };
 });
 
 return [active, setActive];
};
```
Here, I have a state `active` that is set to false anytime a user clicks outside the target ref.

Using the hook in my dropdown component

```javascript
const Dropdown = () => {
 const targetRef = useRef(null);
 const [ menuOpen, setMenuOpen ] = useClickOutside(targetRef);
 
 const onMenuBtnClick = () => {
   setMenuOpen(!menuOpen);
 };
 
 return (
   <div ref={targetRef} className="dropdown-wrapper">
     <button className="openMenu" type="button" onClick={onMenuBtnClick}>
       Open Dropdown Menu
     </button>
     {menuOpen && (
       <div className="dropdown-items-wrapper">
         <h3>Menu Items</h3>
         <ul className="dropdown-items">
           <li className="dropdown-item">Dropdown Item 1</li>
           <li className="dropdown-item">Dropdown Item 2</li>
           <li className="dropdown-item">Dropdown Item 3</li>
         </ul>
       </div>
     )}
   </div>
 );
};
```
Now, I can use this hook wherever I need to act after a user clicks outside a particular element.

I’ve also put a full illustration [here](https://github.com/adaezeodurukwe/customhook)

I hope you enjoyed reading this, feel free to comment on what you think or what you’ve tried or, how you would have solved this.

**References and further reading** 
[Using a Custom Hook](https://reactjs.org/docs/hooks-custom.html)
[Detect click outside React component]( https://stackoverflow.com/questions/32553158/detect-click-outside-react-component)
[React Click Outside](https://www.npmjs.com/package/react-click-outside)