TypeScript | react-dnd

샘샘·2024년 12월 30일
1

TypeScript

목록 보기
13/13

라이센스를 구매해서 사용하고 있는 Kendo에서 기본으로 제공하는 드래그 훅을 사용하려고 했더니
옮겨지긴 하지만, 한번씩 이런 에러가 떴다.

그럼 못 쓰지,, 직접 구현을 하려다가 dragdrop훅을 모두 제공하는 라이브러리를 찾기로 했다.


상황

캘린더 내부 일정을drag하는 것과 각 셀에 drop하는 함수 둘 다 필요하다.
일정이 있는 날이 있고 없는 날이 있는데, 없는 날에 drop했을 시에 해당 날짜를 받아와서 setData를 해줘야 하기 때문이다.

기존 다른 페이지에서 사용했던 단순 순서 바꾸는 드래그훅은 이미 있다.
그런데 지금은 순서가 아니라 캘린더 내부에서 일정을 바꾸는 것이기 때문에 구조가 조금 다르다.

딱 이런 라이브러리가 필요했다
빈 dropBox에 드래그해서 소속을 바꿔버리는...(?)

React DnD는 Flux 데이터 패턴을 활용하고 액션과 리듀서(React와 무관)를 사용하여 드래그 앤 드롭 상태를 모델링하도록 설계되었습니다. 후크는 React에서 상태가 있는 데이터 소스를 활용하는 완벽한 방법입니다.
react-dnd

마침 딱 맞기도 하고 이미 프로젝트에 설치가 되어있었던 react-dnd를 사용하기로 했다.

코드

  const tagRef = React.useRef<HTMLElement>(null);
  const slotRef = React.useRef<SchedulerSlotHandle>(null);
 <SchedulerSlot
      ref={slotRef}
      {...props}
      selected={false}
      style={{
        ...props.style,
      }}
    >
// ....중략
      {items
        ? items.map((i, idx) => {
            const { title, dataItem } = i;
            if (!title) return null;
            const [beforeAsterisk, afterAsterisk] = title.split("*");
            return (
              <React.Fragment key={idx}>
                <TagWrap
                  key={dataItem.id}
                  ref={tagRef}
                  onMouseEnter={() => {
                    setHover(true);
                  }}
                  onMouseLeave={() => setHover(false)}
                  onClick={handlePopover}
                  hover={hover || Boolean(openPopover) || dragged}
                  className={dragging ? `dragged${dataItem.id}` : ""}
                >
// ....생략

각 일정 태그에는 tagRef를, 캘린더의 셀에는 slotRef를 ref로 넣어줬다.

import { useDrag, useDrop } from "react-dnd";
// ....중략
  const [dragging, setDragging] = React.useState(false);
  const [{ isDragging }, drag] = useDrag({
    type: "tag",
    item: () => {
      if (!tagRef.current) return null;
      const updatedClassName = (
        tagRef.current.className.includes("dragged")
          ? tagRef.current.className.split(" ")[0]
          : ""
      ) as string;
      const dragId = updatedClassName.replace("dragged", "");
      handleSetDraggedItemId(parseInt(dragId));
      return { dragId };
    },
    collect: (monitor) => ({
      setDragging: setDragging(true),
      isDragging: monitor.isDragging(),
    }),
    end: (item, monitor) => {
      setDragging(false);
    },
  });

useRef를 선언한 아래에 useDrag를 불러왔다.
드래깅 될 요소에 ref를 할당하고, 어떠한 동작을 할 것인지 선언하는 훅이다.

  • drag
    useRef와 같은 역할
  • type
    드래깅 될 요소가 어떤 타입인지 선언
    (useDrag의 type과 useDrop의 accept가 같아야 함)
  • item
    드래깅 되는 요소에 전달해줄 정보
  • collect
    드래그 되고 있을 때의 동작
  • end
    드래그가 끝났을 때의 동작

isDragging은 드래그를 하는 중인지 아닌지 boolean을 반환하는 것인데.. 이상하게 계속 false가 나와서 따로 state를 만들어서 collect(드래그 중)와 end(드래그 끝)에 setDragging으로 설정해줬다.
왜 그런것인지 원인은 찾는 중..!!

className={dragging ? `dragged${dataItem.id}` : ""}

현재 드래그 되고 있는 tagRef의 className에 dragged + id를 넣어준다.
이렇게 해서 id를 받을 수 있었고 이 id를 handleSetDraggedItemId함수에 파라미터로 전달해서 아래에 나오는 onDrop함수에서 setData를 할 수가 있었다.

  // 데이터 변경 이벤트(kendo scheduler 함수)
  const handleDataChange = ({
    created,
    updated,
    deleted,
  }: SchedulerDataChangeEvent) => {
    setData((old) =>
      old
        // Filter the deleted items
        .filter(
          (item) =>
            deleted.find((current) => current.id === item.id) === undefined,
        )
        // Find and replace the updated items
        .map(
          (item) => updated.find((current) => current.id === item.id) || item,
        )
        // Add the newly created items and assign an `id`.
        .concat(created.map((item) => Object.assign({}, item, { id: guid() }))),
    );
  };

  const onDrop = (newStart: Date, newEnd: Date) => {
    const draggedItem = data.filter((i) => {
      const { id } = i;
      return id === draggedItemId;
    });
    const updatedItem = draggedItem.map((i) => {
      return {
        ...i,
        start: newStart,
        end: newEnd,
      };
    });
    // 드래그 후 업데이트된 항목을 handleDataChange에 전달
    handleDataChange({
      created: [],
      updated: updatedItem, // 수정된 항목
      deleted: [],
    });
  };

  const [, drop] = useDrop({
    accept: "tag",
    drop: () => {
      if (slotRef.current === null) {
        return;
      }
      onDrop(
        new Date(start.setHours(0, 0, 0, 0)),
        new Date(start.setHours(23, 59, 59, 999)),
      );
    },
    collect: (monitor) => ({
      isOver: monitor.isOver(),
    }),
  });

드래그 된 요소가 놓여질 곳에 drop이라는 ref를 할당하고, useDrop훅으로 드롭됐을 때 어떤 행위를 할 것인지 선언해준다.


🥲

kendo UI의 Scheduler를 쓰고 있었기 때문에 Drag&Drop도 kendo의 훅을 쓰고 싶었으나,,, 며칠째 해결하지 못하고 있어서 그냥 빨리 다른 방법을 찾자! 하고 도입한 react-dnd이다.

useDraggable(tag, {
  onDragStart: handleDragStart,
   onDrag: handleDrag,
   onDragEnd: handleDragEnd,
});

예상되는 원인은 tag(useRef)가 null일 때, useDraggable hook을 호출해서 에러가 나는 것 같은데...
드래그함수에 if(tag.current === null) return;, if(isNull(tag)) reutrn;, if(!tag.current) return; 등등 별 짓을 다해봐도 에러 메세지를 없앨 수 없었다.

켄도에 문의를 보내봐도 답변을 받을 수 없음.....

저 훅을 useEffect 내부에 넣어도 봤지만 또 다른 에러를 만들었다!!🤦‍♀️

암튼 다사다난했던 드래그 앤 드롭 구현기,,, -끝-

다음엔,,, 일정 태그를 늘려서 일정 범위 설정하는 것을 구현해야 한다...

profile
회계팀 출신 FE개발자 👉콘테크 회사에서 웹개발을 하고 있습니다

0개의 댓글