React

[React] 이중모달 구현하기

dev_seon 2022. 12. 29. 23:07

이중모달을 구현했던 과정을 정리해보겠습니다. 

 

먼저, 제가 구현하고자 했던 이중 모달의 형태는 아래 그림과 같이 모달 형식의 글 상세 페이지 내에서 댓글을 추가하거나, 수정하거나, 삭제하기 위한 드롭다운 디자인의 이중 모달입니다.

 

댓글 추가, 수정, 삭제를 위한 이중 모달(드롭다운 디자인) 실제 구현 화면

 

이중 모달이 닫힌 상태에서 위쪽의 점 버튼을 누르면 이중 모달이 열리고, 이중 모달이 열린 상태에서 점 버튼을 다시 누르거나 이중 모달 외부의 영역을 클릭했을 때 이중 모달이 닫히는 기능을 구현하고자 했습니다.

 

구현은 점 영역 이외의 문서 전체 영역에서 click 이벤트가 발생했을 때 addEventListener로 이중 모달이 닫히는 함수를 호출하는 방식으로 진행하였고, 상세 코드는 아래와 같습니다.

 

// useDropDown.tsx

function useDropDown() {
  const [isDrop, setIsDrop] = useState(false);
  const ref = React.useRef<HTMLDivElement>(null);

  // 이중 모달을 바깥쪽 클릭 시 이중 모달을 닫는 함수
  // event가 발생했을 때 event의 target이 ref.current를 포함하는지 확인함
  const handleClickOutside = (e: MouseEvent) => {
    if (ref.current && !ref.current.contains(e.target as Node)) {
      setIsDrop(false);
    }
  };

  // 이중 모달 open 상태를 변경하는 함수
  const handleChangeDrop = (value?: boolean) => {
    if (value) {
      setIsDrop(value);
      return;
    }
    setIsDrop((pre) => !pre);
  };
  
  // useEffect hook을 사용하여 화면 전체에 click 이벤트가 발생할 때 handleClickOutside 함수 실행
  useEffect(() => {
    document.addEventListener("click", handleClickOutside);
    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  }, []);

  return {
    isDrop,
    ref,
    handleChangeDrop,
  };
}

export default useDropDown;
// Comment.tsx

const Comment = ({ data }: CommentProps) => {
  const { isDrop, ref, handleChangeDrop } = useDropDown();

  return (
    <CommentContainer>
      <CreateInfo>
        {isDrop && <CommentDropDown />} // isDrop이 true일때 이중 모달 open
        <div className="info">
          <ProfileImg />
          <div className="author">{data.nickName}</div>
          <AuthorOccupationTag occupation={data.occupation} />
          <div className="created-at">{data.createdAt}</div>
        </div>
        // 점 영역을 ref로 설정, 점 외부의 영역 클릭 시 handleClickOutside 함수 동작
        <div ref={ref}>
          <Image
            src={ThreeDots}
            alt="edit"
            onClick={() => {
              handleChangeDrop(!isDrop);
            }}
          />
        </div>
      </CreateInfo>
      <p>{data.content}</p>
    </CommentContainer>
  );
};

 

다만, 위와 같이 코드를 작성하게 되면, 이중 모달을 클릭했을 때에도 이중 모달이 닫히는 문제가 발생했습니다.

때문에 console.log로 이중 모달을 클릭했을 때 동작하는 함수를 확인해보았고, 아래와 같이 이중 모달 영역을 클릭했을 때 handleClickOutside 함수가 함께 실행되는 것을 확인할 수 있었습니다.

 

 

이는 이중 모달 영역을 클릭했을 때 이중 모달의 상위 요소인 글 상세 페이지 모달 영역에도 이벤트가 전달되는 이벤트 버블링 때문임을 확인하였고, 아래와 같이 이중 모달의 Container 영역에 event.stopPropagation() 메서드를 추가하여 해결할 수 있었습니다.

 

const CommentDropDown = () => {
  return (
    <Container
      onClick={(e) => {
        e.stopPropagation();
      }}
    >
      // 모달 내용 코드
    </Container>
  );
};

export default CommentDropDown;