ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 이중모달 구현하기
    React 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;

     

    댓글

Designed by Tistory.