React Drag and Drop 리스트 안에서 컴포넌트의 순서 바꾸기: react-dnd 사용

18642 단어 ReactReact

현재 ToDoBoxList안에 ToDoBox가 있는 형태이다.

ToDoBoxList 내에서 ToDoBox를 drag&drop하여 순서를 변경해보려고 한다.

사용한 라이브러리: react-dnd
https://react-dnd.github.io/react-dnd/about

제공되는 간단한 예시 codesandbox
https://codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_js/04-sortable/cancel-on-drop-outside?from-embed=&file=/src/Card.jsx

드래그하여 옮기려는 컴포넌트(ToDoBox.js) 코드를 처음에는 다음과 같이 작성하였다.

const [{ isDragging }, dragRef, previewRef] = useDrag(() => ({
// isDragging은 드래그 되는 중인지, dragRef는 선택시 드래그되는 영역, previewRef는 드래그시 보여지는 preview
        type: ItemTypes.ToDoBox, // example 코드샌드박스에서처럼 따로 파일을 생성하여 명시했다.
        item: { id, index }, // 드래그되는 아이템에 들어가는 정보
        collect: (monitor) => ({
            isDragging: monitor.isDragging(),
        }),
        end: (item, monitor) => { // 드래그가 끝났을 때 실행
            const { id: originId, index: originIndex } = item;
            const didDrop = monitor.didDrop();
            if (!didDrop) { // dropRef가 연결되어있는 태그에 drop되지 않았을 경우엔
                moveToDoBox(originId, originIndex, false); // 원래 자리로 되돌려놓는다.
            }
        },
    }), [id, index, moveToDoBox]);

    const [, dropRef] = useDrop({
        accept: ItemTypes.ToDoBox,
        hover: ({ id: draggedId, index: orgIndex }) => {
            if(draggedId !== id) // hover되는 box의 id와 드래그 되는 box의 id가 다르면 해당 자리로 box를 옮긴다.
                moveToDoBox(draggedId, index, true);
        }
    })
    ```


하지만 hover 될때 component들이 너무 빨리 업데이트 되어 DnD가 따라잡지 못하는 문제가 발생하여

'react index.ts:28 Uncaught Invariant Violation: Expected to find a valid target. targetId=T2' 와 같은 오류가 발생했다.

이를 해결하기 위해 lodash에서 _를 import하고 hover를 처리하기 위해 debounce를 추가하였다.

참고: https://stackoverflow.com/questions/64894806/expected-to-find-a-valid-target-react-dnd

hover 해결 코드(ToDoBox.js)
```javascript
const [{ isDragging }, dragRef, previewRef] = useDrag(() => ({
        type: ItemTypes.ToDoBox,
        item: { id, index },
        collect: (monitor) => ({
            isDragging: monitor.isDragging(),
        }),
        end: (item, monitor) => {
            const { id: originId, index: originIndex } = item;
            const didDrop = monitor.didDrop();
            if (!didDrop) {
                moveToDoBox(originId, originIndex, false); 
            }
        },
    }), [id, index, moveToDoBox]);

    const debounceHoverItem = _.debounce((draggedId, index, shouldCallApi)=> {
        if(draggedId === id) {
            return null;
        }
        moveToDoBox(draggedId, index, shouldCallApi)
    }, 70);

    const [, dropRef] = useDrop({
        accept: ItemTypes.ToDoBox,
        hover: ({ id: draggedId, index: orgIndex }) => {
            debounceHoverItem(draggedId, index, true); 
        }
    })
    ```
    
ToDoBoxList에서는 ToDoBox로 moveToDoBox라는 함수를 넘겨준다.
ToDoBoxList 내에서 옮기려는 ToDoBox의 순서를 변경해주는 함수이다.


ToDoBoxList.js 코드
```javascript
const moveToDoBox = async (toDoBoxId, toIndex, shouldCallApi) => {
        let box;
        toDoBoxList.forEach((toDoBox)=>{ //filter사용시 계속 오류가 나서 일단 이렇게 작성하였다.
            console.log(toDoBox);
            if(toDoBox.id === toDoBoxId)
                box = toDoBox;
        })
        const index = toDoBoxList.indexOf(box);
        let newOrder = [...toDoBoxList];
        newOrder.splice(index, 1);
        newOrder.splice(toIndex, 0, box);
        setToDoBoxList(newOrder)
        
        if(shouldCallApi) {
            // 서버에 순서 저장 api 요청
        }
    };

결과

좋은 웹페이지 즐겨찾기