react-beautiful-dnd로 리스트 아이템(li)을 움직이려면 다음 두 가지를 모두 적용해야 합니다.
Droppable(드랍 영역) 역할을 하는 컨테이너(
ul)에
provided.droppablePropsprovided.innerRef- 그리고 렌더링 끝에
{provided.placeholder}
를 넣어야 합니다.Draggable(드래그 대상) 역할을 하는 요소(
li)에
provided.draggablePropsprovided.dragHandlePropsprovided.innerRef
를 넣어야 드래그가 가능해집니다.
provided.droppableProps
provided.innerRef
provided.placeholder
import { Droppable } from "react-beautiful-dnd";
<Droppable droppableId="my-list">
{(provided) => (
<ul
/* 1) 드랍 영역 ref 연결 */
ref={provided.innerRef}
/* 2) 드랍 속성들 스프레드 */
{...provided.droppableProps}
>
{items.map((item, index) => (
/* …Draggable… */
))}
{/* 3) 드래그 시 리스트 레이아웃 유지를 위한 placeholder */}
{provided.placeholder}
</ul>
)}
</Droppable>
provided.draggableProps
provided.dragHandleProps
provided.innerRef
import { Draggable } from "react-beautiful-dnd";
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => (
<li
/* 1) 드래그 대상 ref 연결 */
ref={provided.innerRef}
/* 2) 드래그 동작에 필요한 속성들 스프레드 */
{...provided.draggableProps}
/* 3) 손잡이(Drag Handle) 역할 속성 스프레드 */
{...provided.dragHandleProps}
>
{item.content}
</li>
)}
</Draggable>
import React from "react";
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
function App() {
const [items, setItems] = React.useState([
{ id: "1", content: "첫 번째 아이템" },
{ id: "2", content: "두 번째 아이템" },
// …
]);
const onDragEnd = (result: DropResult) => {
if (!result.destination) return;
const updated = Array.from(items);
const [moved] = updated.splice(result.source.index, 1);
updated.splice(result.destination.index, 0, moved);
setItems(updated);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable-ul">
{(provided) => (
<ul
ref={provided.innerRef}
{...provided.droppableProps}
style={{ listStyle: "none", padding: 0 }}
>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: "8px",
margin: "0 0 8px 0",
background: "#eee",
...provided.draggableProps.style,
}}
>
{item.content}
</li>
)}
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
);
}
export default App;
provided.innerRef
...provided.droppableProps
{provided.placeholder}
<Droppable droppableId="list">
{(provided) => (
<ul
ref={provided.innerRef} // 필수: ref 연결
{...provided.droppableProps} // 필수: 드랍 속성
>
{items.map(/* …Draggable… */)}
{provided.placeholder} // 필수: 레이아웃 유지용
</ul>
)}
</Droppable>
provided.innerRef
...provided.draggableProps
...provided.dragHandleProps
<Draggable key={item.id} draggableId={item.id} index={i}>
{(provided) => (
<li
ref={provided.innerRef} // 필수: ref 연결
{...provided.draggableProps} // 필수: 드래그 속성
{...provided.dragHandleProps} // 필수: 잡을 수 있는 영역 지정
>
{item.content}
</li>
)}
</Draggable>
모두 있어야 정상 동작
innerRef, droppableProps, placeholderinnerRef, draggableProps, dragHandleProps하나라도 빠지면 드래그·드롭 중 인식, 레이아웃 유지, 제어 핸들링 등에서 오류가 생깁니다.
draggableProps와 dragHandleProps 역할provided.draggableProps무엇을 하나요?
transform, transition)과 ARIA 속성들을 포함합니다.사용 예시
<li
ref={provided.innerRef}
{...provided.draggableProps} // 이 부분이 없으면, 드래그는 시도되지만 움직이지 않거나 레이아웃이 꼬집니다.
>
…
</li>
provided.dragHandleProps무엇을 하나요?
이 속성이 없으면?
draggableProps로 스타일은 적용되지만, “어디서 잡아야 드래그가 시작되는지”에 대한 이벤트가 연결되어 있지 않아 드래그가 동작하지 않습니다.사용 예시
<li
ref={provided.innerRef}
{...provided.draggableProps} // 위치 제어용
>
{/* 이 span을 잡아야만 드래그가 시작된다 */}
<span {...provided.dragHandleProps}>☰</span>
아이템 내용
</li>
draggableProps와 dragHandleProps를 같은 요소에 모두 적용하면, 그 요소 전체가 드래그 핸들이 됩니다.
<li
ref={provided.innerRef}
{...provided.draggableProps} // 움직임 스타일
{...provided.dragHandleProps} // 클릭 영역 전체를 핸들로
>
전체를 잡고 드래그 가능
</li>
draggableProps
dragHandleProps
→ 둘 중 하나라도 빠지면
draggableProps 없이: 움직임 자체가 적용되지 않음dragHandleProps 없이: 잡아도 드래그 시작 이벤트가 연결되지 않아 동작하지 않음실제 코드에선 보통 이렇게 씁니다:
<Draggable draggableId={id} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.content}
</li>
)}
</Draggable>
이처럼 두 가지를 구분해서 쓰는 이유는,