라이센스를 구매해서 사용하고 있는 Kendo에서 기본으로 제공하는 드래그 훅을 사용하려고 했더니
옮겨지긴 하지만, 한번씩 이런 에러가 떴다.
그럼 못 쓰지,, 직접 구현을 하려다가 drag
와 drop
훅을 모두 제공하는 라이브러리를 찾기로 했다.
캘린더 내부 일정을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
내부에 넣어도 봤지만 또 다른 에러를 만들었다!!🤦♀️
암튼 다사다난했던 드래그 앤 드롭 구현기,,, -끝-
다음엔,,, 일정 태그를 늘려서 일정 범위 설정하는 것을 구현해야 한다...