useClickedOutside React Hook in TypeScript

Wed, 13 Feb 2019

Sandbox: https://codesandbox.io/s/n3ymz0rzvj?fontsize=14

import React, { useState, useRef, RefObject, useEffect } from 'react'

/**
 * Provides a hook to detect when a user clicks outside a component.
 * @param callback - the function to call when user clicks outside of the referenced component.
 * @returns a ref to be applied to the target element.
 */
const useClickedOutside = (callback: (e: Event) => void): RefObject<any> => {
  // Set-up the reference that'll be used to refer to the component.
  const ref = useRef<HTMLElement>(null);

  // Called by the event listener below.
  const handleClick: EventListener = event => {
    // Not clicked on this component nor its children.
    if (!ref.current || !ref.current.contains(event.target as Node)) {
      callback(event);
    }
  };

  useEffect(() => {
    // Create event listener when mounting the component.
    document.addEventListener("click", handleClick, true);
    return () => {
      // Destroy event listener when unmounting the component.
      document.removeEventListener("click", handleClick, true);
    };
  }, []); // Pass an empty array as the second paramter to avoid useEffect being re-run on component updates.

  return ref;
};

const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  const hide = () => setIsOpen(false);
  const flip = () => setIsOpen(!isOpen);
  const componentRef = useClickedOutside(hide);

  return (
    <div
      ref={componentRef}
      style={{
        border: "1px solid white",
        display: "inline-block",
        padding: 20
      }}
    >
      <button onClick={flip}>{isOpen ? "Close" : "Open"}</button>
      {isOpen && <p>Click outside this box to hide this text.</p>}
    </div>
  );
};

export default App