dev

React Custom Popover Component

간단한 Popover 컴포넌트 만들기

Popover 컴포넌트 데모

- 마우스를 Block1에 hover하면 Opener가 나타나고 Opener를 클릭하면 Popper가 나타난다.
- Popper를 제외한 영역을 클릭하면 Opener와 Popper가 사라진다.

우선, Material-UI 사이트의 Popover 컴포넌트가 어떻게 구현되어 있는지 확인했다.

Material-UI 사이트의 Popover 컴포넌트는 다음과 같이 설명되어있다.

Things to know when using the Popover component:
The component is built on top of the Modal component.
The scroll and click away are blocked unlike with the Popper component.

참고 | Material-UI Popover

Popper 컴포넌트가 나타나있는 동안 클릭과 스크롤이 막힌다는 내용이었다. Modal 컴포넌트를 기반으로 만들었기 때문인 것 같았다.
필요한 Popover 컴포넌트는 Popper가 나타나있는 동안 스크롤과 클릭이 막히면 안되기 때문에 활용할 수 없었다.

필요한 Popover 기능을 정리하면 다음과 같다.

  1. Block1에 마우스를 hover 하면 Opener가 나타난다.
  2. Opener를 클릭하면 Popper가 나타난다.
  3. Popper가 나타나있는 동안 클릭과 스크롤이 가능해야 한다.
  4. Popper 밖의 영역을 클릭하면 Opener와 Popper가 사라져야 한다.

먼저, React 공식 문서에 정리(clean-up)를 이용하는 Effects 부분을 읽어보고 도움이 많이 되었다. useEffect에서 함수를 반환하면 해당 함수는 다음 렌더링 전에 실행된다는 것이 핵심이다.

구현한 코드를 살펴보면 Popper 컴포넌트가 화면에 나타나면 useEffect안에 이벤트 리스너가 pageClickEvent를 콜백함수로 가지는 클릭 이벤트를 등록한다. DOM 최상단에 클릭 이벤트가 등록되었기 때문에 화면에 어디를 클릭해도 pageClickEvent 콜백함수가 실행된다.

또한, useEffect에서 해당 클릭 이벤트를 제거하는 함수를 리턴하고 있기 때문에 클릭 이벤트가 발생해서 화면이 다시 렌더링 되면 클릭 이벤트가 제거된다.

export const Popover =
  React.memo <
  Props >
  (props => {
    const { onClose, children } = props;

    const settingsWindowRef = useRef < HTMLDivElement > null;

    useEffect(() => {
      const pageClickEvent = (e: any) => {
        if (!settingsWindowRef.current.contains(e.target)) {
          onClose();
        }
      };

      window.addEventListener('click', pageClickEvent, true);

      return () => {
        window.removeEventListener('click', pageClickEvent, true);
      };
    });

    return <Wrapper ref={settingsWindowRef}>{children}</Wrapper>;
  });

Popper를 useRef를 사용해서 pageClickEvent 함수에서 제외하는 이유는 설정 창 안 내용을 클릭했을 때는 Popper가 닫히면 안되기 때문이다.