Detecting clicks outside an HTML element using the custom React Hook

Detecting clicks outside an HTML element using the custom React Hook

Implementing a Custom Hook to Detect Outside Clicks

·

4 min read

In February 2019 React introduced Hooks. Which empowered the Functional components to have the same powers/features which were only present in React Class component before React 16.8.0. The introduction of hooks gave function components the ability to use state and side-effects with built-in Hooks such as React's useState Hook and React's useEffect Hook.

There are only a handful of built-in hooks in React. However, react also give the developer the option to develop their own Hooks which react states as Custom Hooks. By using the Hooks provided by react as a foundation developers can develop their own hooks that can contain stateful logic.

Before we create a custom hook, you need to know that there are two rules to creating one:

  • Custom hooks must have "use" as a prefix. It is how React knows to start the logic contained in the Hook and make the returned state and values available to the component. For example useLocalStorage or useBoolean etc. In our case, the custom hook will be named useOutSideClick.

  • The custom hook is built using built-in React hooks. Therefore custom hook is always a new composition of one or multiple hooks. If a hook doesn't use any hook then it's not a custom hook It will just be a plain javascript function. So in short if you want to create a custom hook then you should use React hooks inside of it.

To explain further what custom hooks are I am going to create a custom hook that will detect if a click has happened outside of an HTML element or a react component.

Implementing the Hook

we are going to create a hook that is going to detect if a click has occurred outside the desired component. This kind of hook is really useful when you are building a Dropdown component, menu or modal. You want to close the window when a click has occurred outside of the modal.

import { MutableRefObject, useEffect } from 'react';

type OutsideNotifierHook = (
    ref: MutableRefObject<any>,
    onOutsideClick: (isInside: false) => void
) => void;

const useOutSideClick: OutsideNotifierHook = (ref, onOutsideClick) => {
    useEffect(() => {
        const handleClick = (e: MouseEvent) => {
            if (ref.current && !ref.current.contains(e.target)) {
                onOutsideClick(false);
            }
        };
        document.addEventListener('mousedown', handleClick);
        return () => document.removeEventListener('mousedown', handleClick);
    }, [ref, onOutsideClick]);
};

export default useOutSideClick;

The code defines a custom hook named useOutSideClick. The Hook allows you to detect clicks outside a specified HTML element and trigger a callback when this occurs.

The hook takes two arguments, ref and onOutSideClick.

  • ref: ref is of type MutableRefObject that refers to the HTML element you want to track the outside click.

  • onOutSideClick: this is a callback function that is fired when the click has occurred outside of the element passed as the first argument.

useEffect hook is used to handle the side effect of the component. In this case, add and remove the mouse event listener to the document object.

handleClick function is defined in the useEffect which is passed as the second argument to the event listener. This means that it is going to be triggered when the outside click will occur. It will also determine if the click has occurred outside or inside the ref argument. If the click has occurred outside of the ref argument then it will trigger the callback with false as an argument.

The return argument in the useEffect is to remove the event listener when the component that uses this hook is unmounted.

In the end we are exporting our hook for it to be accessible where we want to use it.

Implementing the Hook

import { useRef, useState } from "react";
import "./styles.css";
import useOutsideClick from "./useOutsideClick";

export default function App() {
  const ref = useRef<any>(null);
  const [visible, setVisible] = useState(false);
  useOutsideClick(ref, setVisible);
  return (
    <>
      <button ref={ref} onClick={() => setVisible(true)} className="drop-down">
        This is a drop down
      </button>
      {visible ? (
        <ul>
          <li>Item 1 </li>
          <li>Item 2 </li>
          <li>Item 3 </li>
          <li>Item 4 </li>
        </ul>
      ) : null}
    </>
  );
}

In this code, we are utilizing our custom hook to hide the list element when the click happens outside of the button component.

useRef: A react built-in Hook to create a reference for the specified element. In our case, we are using it to create a reference for the button element. Which latter on we will pass to our custom hook as a first argument.

visible: we have declared a state variable named visible which we are using to display our list conditionally. If it is false the list will not be visible and if it's set to true, the list will be rendered.

useOutsideClick: First we are importing our cusotm hook from the file name useOutsideClick. Then we are passing two arguments to it. The first one is the reference to the element we want to track click for. and the second is the callback which is going to be triggered once the click occurs outside of the button.

Conclusion

Hooks in React allow developers to add stateful logic to the functional component, providing them with the same capabilities as the class component. The introduction of custom hooks has further extended the possibilities, As developer can build their own hooks and reuse the stateful logic between different components.