onDragEnter : 드래그 앤 드롭시 아래에 있는 컴포넌트 색상 바꾸기

chichi·2023년 5월 17일
1

motion 프로젝트를 진행 하면서, 드래그앤 드롭시 아래에 있는 컴포넌트 색상을 바꾸고 싶어서 찾아본 것에 대해 기록하고자 한다.

오늘 하루를 꼬박 다 써서 찾았는데 의도한 대로 작동하지 않아 애를 먹었다. 처음에 했던 방식으로는, 드래그를 시작한 컴포넌트가 계속 색상이 바뀌어 있거나, 아예 바뀌지 않는 등 여러 방법으로 시행 착오를 꽤 겪었었다.

결론은 mdn 문서에 정답이 있었고, 앞으로 잘 모르는 기능에 대해선 무조건 공식 문서 및 mdn 문서를 찾아본 뒤 해결하는 습관을 필수로 길러야겠다.

오답

처음에는 onDragEnter의 존재를 모르고 getDragId 같은 함수를 만든 뒤, getData로 아이디를 알아내고, 현재 아이디와 getDragId 함수의 리턴 값의 아이디가 같다면 색상이 변경되게 만들어야 하는 줄 알고 잘못된 코드를 썼었다.


// 잘못된 코드

const getDragId = (event: React.DragEvent<HTMLDivElement>) => {
    const dropId = Number(event.dataTransfer.getData("text/plain"));
    return dropId;
  };

		<div
          draggable
          onDragStart={(event) => {
            onDragStart(event, id.toString());
            const newDragId = getDragId(event);
            setDragId(newDragId); // useState로 dragId 값을 업데이트
            console.log("id:", id);
            console.log("dragId:", dragId);
          }}
          onDragOver={(event) => onDragOver(event)}
          onDrop={(event) => {
            onDrop(event, index);
          }}
          className={`flex post p-5 m-3 border hover:border-red-200 rounded-lg relative ${
            dragId === id ? "bg-red-200" : "" // dragId와 id 값이 같으면 배경 색상을 변경하게 만들었다
          }`}
        >

이렇게 썼더니 드래그를 시도한 모든 게시글의 배경이 bg-red-200 색상으로 바뀌었고, 드래그를 놓아도 bg-red-200 색상에서 변경되지 않았다.

내가 의도했던 동작과 다르게 되어 더 찾아본 결과 onDragEnter라는 기능이 있음을 알게 되었다.

해결 : onDragEnter

onDragEnter는 JavaScript에서 제공하는 이벤트 핸들러 중 하나입니다. 이 이벤트 핸들러는 드래그된 요소가 다른 요소 위로 들어갈 때 발생합니다. 일반적으로 드래그 앤 드롭(Drag and Drop) 기능을 구현할 때 사용되며, 드래그된 요소가 드롭 대상 요소 위로 진입할 때 원하는 동작을 수행할 수 있습니다.

예를 들어, 사용자가 마우스로 요소를 드래그하면 해당 요소에 onDragEnter 이벤트가 발생합니다. 이 때, 드래그되는 요소가 다른 요소 위로 진입할 때마다 onDragEnter 이벤트 핸들러가 호출됩니다. 이를 통해 드롭 대상 요소에 특정 스타일을 적용하거나 상태를 변경하는 등의 작업을 수행할 수 있습니다.

onDragEnter 이벤트는 HTML5의 드래그 앤 드롭 API와 관련이 있으며, 일반적으로 <div>, <span>, <li>와 같은 요소에서 사용됩니다. 이벤트 객체를 통해 드래그 중인 요소와 진입한 요소의 정보를 확인하고, 원하는 작업을 수행할 수 있습니다.

mdn에서도 드래그 앤 드롭 관련 참고 자료를 찾을 수 있었다.

https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API

그래서 useDrag훅에setDragEnteredId를 사용할 수 있도록 인터페이스를 만들고,
useDrag에 onDragEnter를 다음과 같이 작성했다.

import { postList } from "../store/content";
import { DragPropsType } from "../types/contentsType";

interface Props extends DragPropsType {
  setDragEnteredId: React.Dispatch<React.SetStateAction<number | null>>;
}

export const useDrag = ({ posts, dispatch, setDragEnteredId }: Props) => {
  // onDragEnter : 마우스가 컨텐츠 위로 들어갈 때 dragId 값을 업데이트
  const onDragEnter = (
    event: React.DragEvent<HTMLDivElement>,
    enteredId: number
  ) => {
    event.preventDefault();
    setDragEnteredId(enteredId);
  };

  // onDragStart : 드래그 이벤트를 처리하는 함수
  // 이벤트 객체의 dataTransfer 프로퍼티에 id를 text/plain 형태로 저장
  // 이렇게 하면 드래그된 아이템을 드랍할 때 id를 전달할 수 있다
  const onDragStart = (
    event: React.DragEvent<HTMLDivElement>,
    dragId: string
  ) => {
    event.dataTransfer.setData("text/plain", dragId);
  };

  const onDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
  };

  // onDrop : 드롭 이벤트를 처리하는 함수
  // 드롭 대상 요소에서 호출됨. onDrop 이벤트 핸들러를 등록하면 해당 요소 위에 드래그된 아이템이 등록될 때 이 함수가 호출된다
  // 매개변수로 이벤트 객체와 index를 받는다
  // index : 드롭 대상 요소에서 해당 아이템이 드롭된 위치의 인덱스를 나타냄
  // index를 이용해 드롭된 아이템을 원하는 위치에 삽입 가능
  // event.dataTransfer.getData("text/plain"): 드래그 이벤트가 발생했을 때 설정한 데이터를 가져옴
  // state의 items에서 id와, 드래그 이벤트에서 설정한 데이터의 id가 같은 것을 찾아서
  // item배열에서 item의 id와 일치하지 않는 것을 찾아 newItems를 만든 뒤
  // index 위치에 0개 요소를 삭제하고 item 요소를 추가
  // setItems 함수를 호출, items 배열을 newItems로 업데이트
  const onDrop = (
    event: React.DragEvent<HTMLDivElement>,
    dropIndex?: number
  ) => {
    event.preventDefault();
    if (dropIndex === undefined) {
      return;
    }
    const dropId = Number(event.dataTransfer.getData("text/plain"));
    const item = posts.find((it) => it.id === dropId);
    if (item) {
      const newItems = posts.filter((it) => it.id !== dropId);
      newItems.splice(dropIndex, 0, item);
      dispatch(postList(newItems));
    }
    setDragEnteredId(null);
  };

  return {
    onDragStart,
    onDragOver,
    onDrop,
    onDragEnter,
  };
};

이제 사용할 컴포넌트로 넘어가서, 상위 컴포넌트인 ContentList에 state를 지정하고 넘겨줬다.

const [dragEnteredId, setDragEnteredId] = useState<number | null>(null);

//...

return (
    <>
      <div className=" flex flex-col items-center justify-center">
        {posts.length === 0 && (
          <div className="items-center justify-center text-gray-500">
            등록된 포스트가 없습니다.
          </div>
        )}
        <ul className="w-full">
          {posts.map((post: Post, index: number) => (
            <ContentListItem
              key={post.id}
              category={post.category}
              id={post.id}
              imageUrl={post.imageUrl}
              videoUrl={post.videoUrl}
              task={post.task}
              title={post.title}
              content={post.content}
              index={index}
              dragEnteredId={dragEnteredId}
              setDragEnteredId={setDragEnteredId}
            />
          ))}
        </ul>
      </div>
    </>
  );

그 다음 하위 컴포넌트인 ContentListItem.tsx에서 Props 인터페이스에 dragEnteredId와 setDragEnteredId를 추가했다.

interface Props {
  id: number;
  imageUrl?: string | null | undefined;
  videoUrl?: string | null | undefined;
  task?: boolean | undefined;
  title: string;
  content: string;
  index?: number | undefined;
  category?: string;
  dragEnteredId: number | null;
  setDragEnteredId: React.Dispatch<React.SetStateAction<number | null>>;
}

ContentList에서 받아온 dragEnteredId와 setDragEnteredId를 가져오고,

useDrag의 onDragEnter 함수도 가져온다.

const ContentListItem = ({
  id,
  imageUrl,
  videoUrl,
  task,
  title,
  content,
  index,
  category,
  dragEnteredId,
  setDragEnteredId,
}: Props) => {

//...

const { onDragStart, onDragOver, onDrop, onDragEnter } = useDrag({
    posts,
    dispatch,
    setDragEnteredId,
  });

마지막으로 리턴문의 onDragEnter에서 onDragEnter에 필요한 값을 전달해주고, className의 속성을 변경했다.

return (
    <>
      <li className="flex-grow relative">
        <div
          draggable
          onDragStart={(event) => {
            onDragStart(event, id.toString());
          }}
          onDragEnter={(event) => onDragEnter(event, id)}
          onDragOver={(event) => onDragOver(event)}
          onDrop={(event) => {
            onDrop(event, index);
          }}
          className={`flex post p-5 m-3 border hover:border-red-200 rounded-lg relative ${
            dragEnteredId === id ? "bg-red-200" : ""
          }`}
        >

dragEnteredId와 id가 동일하면 배경 색상이 바뀌고, 그렇지 않으면 배경 색상의 변화가 없게 만들어줬다.

이제 의도한 대로 정상적으로 작동하는 것을 확인할 수 있었다 :)

0개의 댓글