[React] drag leave가 반복적으로 발생할 때

Ell!·2022년 4월 11일
0

react

목록 보기
26/28
post-thumbnail

문제점

input에다가 이미지를 drop시켜서 uploading하는 기능을 구현하던 중에 drag over, drag leaver가 반복적으로 발생하는 이슈 발견

간단한 구조

const App = () => {
  return (
    <div class="parent">
        <input class="child" /> 
    </div>
  );
}

위와 같이 nested된 간단한 구조의 컴포넌트가 있다.

여기에 사진을 drop시켜서 upload하는 기능을 구현해보겠다.

  // 사진이나 영상을 끌고 있을 때.
  const [dragging, setDragging] = useState(false);

  // drop
  const handleDrop = useCallback(e => {
    // Prevent default behavior (Prevent file from being opened)
    e.preventDefault();

    setDragging(() => false); // drag 끝남

    const file = e.dataTransfer.files[0];
    if (file) {
      const reader = new FileReader();
      reader.readAsDataURL(file); // file 읽히고.

      reader.onloadend = () => {
        const dataURI = reader.result;
        setImages(prev => [...prev, dataURI]);
      };
    }
  }, []);


  const handleDragOver = useCallback(e => {
    // Prevent default behavior (Prevent file from being opened)
    e.preventDefault();

    setDragging(() => true); // drag 중
  }, []);

  const handleDragLeave = useCallback(e => {
    setDragging(() => false); // drag 끝남
  }, []);

hanldeDrop 함수에서 사진을 upload하고 저장하는 것까지 완료하였다. 문제는, 이렇게 사진을 끌어올 때, border에 색을 주는 등의 작업을 하고 싶었다.

하지만 parent div에 onDragOver ,onDragLeave 두가지 event에 hanldeDragOverhanldeDragLeave 두 함수를 넣어 실행해보니

위 움짤과 같이 state변경이 반복해서 일어나는 것이었다..!

문제의 원인

함수에 로그를 찍어보니 분명 같은 이벤트가 parent, child 두군데에서 동시에 발생하는 것을 발견할 수 있었다. parent의 div와 child인 input에서 동시에 drag event가 발생하여 일어나는 것이라 생각이 되었다.

더 정확하게는 onDragLeave에서 반복적으로 event가 trigger되는 것이 문제였다.

간단하게 input 하나라고 적어두었지만, react-mentions 라이브러리를 사용해서 만들어준 컴포넌트였기에 내부 구조는 더 복잡하였다.

그중에서

div element가 안쪽에 하나 있었는데, mouse가 parent의 안에는 들어가있지만 내부의 다른 child로 간다고 판단이 되어 자꾸 dragLeave가 발생하는 듯 하다.

이를 해결하기 위해선 두가지 방법이 있다.

해결방법

css 해결법

간단한 해결방법이다. child element에 pointer-events : none을 집어넣어주면 끝난다. 자식 element에게서 더 이상 마우스로 인한 event trigger가 일어나지 않는다.

하지만 이 경우 문제가 있으니, child input을 focus할 수 없다는 것이었다.

js 해결법

위의 방법은 간단하지만 한계가 있었으니, 다른 방법을 찾아보았다. 꽤 오랫동안 키워드를 찾지 못해서 헤맸는데, 결국 나와 같은 문제를 가진 사람을 찾을 수 있었다. 여기!

여기에 나온 해결책은 mouse event의 relatedTarget을 사용하라는 것이었다.

MDN에 나와있는 내용에 따르면 dragLeave event에서 target은 mouse가 나오는 노드를 가리키고 relatedTarget은 mouse가 진입하는 노드를 가리킨다고 한다.

아무튼 간단하게

if (e.currentTarget.contains(e.relatedTarget)) return;

handleDragLeave에 넣어서 해결할 수 있었다. (dragOver에는 넣어도 e.relatedTarget이 null을 return한다)

즉, 내부의 이동은 생각하지 않을게! 라고 제한을 걸어두는 것이다.

profile
더 나은 서비스를 고민하는 프론트엔드 개발자.

0개의 댓글