sujinleeme
4/4/2020 - 9:08 AM

useOnClick.tsx

import { RefObject, useEffect } from 'react';

const MOUSEDOWN = 'mousedown';
const TOUCHSTART = 'touchstart';

type HandledEvents = [typeof MOUSEDOWN, typeof TOUCHSTART];

interface UseOnClickOutsideProps {
  innerRef: RefObject<HTMLElement>;
  outerRef?: RefObject<HTMLElement> | { current: HTMLDocument };
  handler?: {
    innerRef?: () => void;
    outerRef?: () => void;
  };
}

const useOnClickOutside = ({
  innerRef,
  outerRef = { current: document },
  handler,
}: UseOnClickOutsideProps) => {
  useEffect(() => {
    if (!handler || !innerRef) return;

    const outRefListener = (e: Event): void => {
      if (!innerRef.current || !handler || innerRef.current.contains(e.target as Node)) return;
      e.preventDefault();
      e.stopPropagation();
      handler.outerRef && handler.outerRef();
    };

    const innerRefListener = (e: Event): void => {
      e.stopPropagation();
      handler.innerRef && handler.innerRef();
    };

    const events: HandledEvents = [MOUSEDOWN, TOUCHSTART];

    events.forEach(event => {
      outerRef.current && outerRef.current.addEventListener(event, outRefListener);
      innerRef.current && innerRef.current.addEventListener(event, innerRefListener);
    });

    return () => {
      events.forEach(event => {
        outerRef.current && outerRef.current.removeEventListener(event, outRefListener);
        innerRef.current && innerRef.current.removeEventListener(event, innerRefListener);
      });
    };
  }, [handler]);
};

export default useOnClickOutside;