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에 hanldeDragOver
와 hanldeDragLeave
두 함수를 넣어 실행해보니
위 움짤과 같이 state
변경이 반복해서 일어나는 것이었다..!
함수에 로그를 찍어보니 분명 같은 이벤트가 parent, child 두군데에서 동시에 발생하는 것을 발견할 수 있었다. parent의 div와 child인 input에서 동시에 drag event가 발생하여 일어나는 것이라 생각이 되었다.
더 정확하게는 onDragLeave
에서 반복적으로 event가 trigger되는 것이 문제였다.
간단하게 input 하나라고 적어두었지만, react-mentions
라이브러리를 사용해서 만들어준 컴포넌트였기에 내부 구조는 더 복잡하였다.
그중에서
div
element가 안쪽에 하나 있었는데, mouse가 parent의 안에는 들어가있지만 내부의 다른 child로 간다고 판단이 되어 자꾸 dragLeave
가 발생하는 듯 하다.
이를 해결하기 위해선 두가지 방법이 있다.
간단한 해결방법이다. child element에 pointer-events : none
을 집어넣어주면 끝난다. 자식 element에게서 더 이상 마우스로 인한 event trigger가 일어나지 않는다.
하지만 이 경우 문제가 있으니, child input을 focus할 수 없다는 것이었다.
위의 방법은 간단하지만 한계가 있었으니, 다른 방법을 찾아보았다. 꽤 오랫동안 키워드를 찾지 못해서 헤맸는데, 결국 나와 같은 문제를 가진 사람을 찾을 수 있었다. 여기!
여기에 나온 해결책은 mouse event의 relatedTarget
을 사용하라는 것이었다.
MDN에 나와있는 내용에 따르면 dragLeave
event에서 target은 mouse가 나오는 노드를 가리키고 relatedTarget은 mouse가 진입하는 노드를 가리킨다고 한다.
아무튼 간단하게
if (e.currentTarget.contains(e.relatedTarget)) return;
를 handleDragLeave
에 넣어서 해결할 수 있었다. (dragOver에는 넣어도 e.relatedTarget이 null을 return한다)
즉, 내부의 이동은 생각하지 않을게! 라고 제한을 걸어두는 것이다.