use-click-outside

use-click-outside

주어진 요소 또는 요소 그룹 바깥의 클릭 및 터치 이벤트를 감지하는 use-click-outside 훅에 대해 설명해 드릴게요. @mantine/hooks 패키지에서 제공돼요.

출처: 문서

본문

사용법

import { useState } from 'react';
import { Paper, Button } from '@mantine/core';
import { useClickOutside } from '@mantine/hooks';

function Demo() {
  const [opened, setOpened] = useState(false);
  const ref = useClickOutside(() => setOpened(false));

  return (
    <>
      <Button onClick={() => setOpened(true)}>Open dropdown</Button>

      {opened && (
        <Paper ref={ref} shadow="sm">
          <span>Click outside to close</span>
        </Paper>
      )}
    </>
  );
}

API

use-click-outside 훅은 5개의 인자를 받아요:

  • handler — 외부 클릭 시 호출되는 함수
  • events — 외부 클릭을 유발하는 이벤트 목록(선택), 기본값 ['mousedown', 'touchstart']
  • nodes — 외부 클릭 이벤트를 유발하지 않아야 하는 노드 목록(선택)
  • enabled — 리스너를 동적으로 활성화/비활성화하는 boolean(선택), 기본값 true
  • capture — capture 단계에서 리스너를 등록할지 여부(선택), 기본값 false

훅은 외부 클릭을 캡처할 기준이 되는 요소에 전달해야 하는 ref 객체를 반환해요.

import { useClickOutside } from '@mantine/hooks';

function Example() {
  const handleClickOutside = () =>
    console.log('Clicked outside of div');
  const ref = useClickOutside(handleClickOutside);
  return <div ref={ref} />;
}

이벤트 변경하기

기본적으로 use-click-outside는 mousedown과 touchstart 이벤트를 수신해요. 두 번째 인자로 이벤트 배열을 전달해 이 이벤트를 바꿀 수 있어요:

import { useState } from 'react';
import { Paper, Button } from '@mantine/core';
import { useClickOutside } from '@mantine/hooks';

function Demo() {
  const [opened, setOpened] = useState(false);
  const ref = useClickOutside(() => setOpened(false), ['mouseup', 'touchend']);

  return (
    <>
      <Button onClick={() => setOpened(true)}>Open dropdown</Button>

      {opened && (
        <Paper ref={ref} shadow="sm">
          <span>Click outside to close</span>
        </Paper>
      )}
    </>
  );
}

여러 노드

// Will work only with useState, not useRef
import { useState } from 'react';
import { Portal } from '@mantine/core';
import { useClickOutside } from '@mantine/hooks';

function Demo() {
  const [dropdown, setDropdown] = useState<HTMLDivElement | null>(
    null
  );
  const [control, setControl] = useState<HTMLDivElement | null>(null);

  useClickOutside(() => console.log('outside'), null, [
    control,
    dropdown,
  ]);

  return (
    // We cannot use root element ref as it does not contain dropdown
    <div>
      <div ref={setControl}>Control</div>
      <Portal>
        <div ref={setDropdown}>Dropdown</div>
      </Portal>
    </div>
  );
}

Capture 단계

기본적으로 use-click-outside는 bubble 단계에서 이벤트를 수신해요. 요소가 event.stopPropagation()을 호출하면 이벤트가 document에 도달하지 못하고 핸들러가 호출되지 않아요. 다섯 번째 인자로 true를 전달해 대신 capture 단계에서 수신할 수 있어요. 그러면 다른 요소가 이벤트를 막기 전에 핸들러가 호출돼요:

import { useClickOutside } from '@mantine/hooks';

function Demo() {
  const ref = useClickOutside(
    () => console.log('Clicked outside'),
    null,
    undefined,
    true,
    true
  );

  return <div ref={ref} />;
}

ref 타입 설정

import { useClickOutside } from '@mantine/hooks';

const ref = useClickOutside<HTMLDivElement>(() =>
  console.log('Click outside')
);

정의

function useClickOutside<T extends HTMLElement = any>(
  handler: (event: MouseEvent | TouchEvent) => void,
  events?: string[] | null,
  nodes?: (HTMLElement | null)[],
  enabled?: boolean,
  capture?: boolean
): React.RefObject<T>;

더 알아보기 (Learn more)