0
# Drag Sources
1
2
The useDrag hook enables components to act as drag sources, allowing users to drag elements and initiate drag and drop operations.
3
4
## Capabilities
5
6
### useDrag Hook
7
8
Creates a draggable element with comprehensive configuration options for drag behavior.
9
10
```typescript { .api }
11
/**
12
* Hook for making components draggable
13
* @param specArg - Drag source specification (object or function)
14
* @param deps - Optional dependency array for memoization
15
* @returns Tuple of [collected props, drag ref connector, preview ref connector]
16
*/
17
function useDrag<DragObject = unknown, DropResult = unknown, CollectedProps = unknown>(
18
specArg: FactoryOrInstance<DragSourceHookSpec<DragObject, DropResult, CollectedProps>>,
19
deps?: unknown[]
20
): [CollectedProps, ConnectDragSource, ConnectDragPreview];
21
22
interface DragSourceHookSpec<DragObject, DropResult, CollectedProps> {
23
/** The type of item being dragged - required */
24
type: SourceType;
25
/** Item data or factory function - defines what data is available to drop targets */
26
item?: DragObject | DragObjectFactory<DragObject>;
27
/** Drag source options for visual effects */
28
options?: DragSourceOptions;
29
/** Preview rendering options */
30
previewOptions?: DragPreviewOptions;
31
/** Called when drag operation ends */
32
end?: (draggedItem: DragObject, monitor: DragSourceMonitor<DragObject, DropResult>) => void;
33
/** Determines if dragging is allowed */
34
canDrag?: boolean | ((monitor: DragSourceMonitor<DragObject, DropResult>) => boolean);
35
/** Custom logic for determining drag state */
36
isDragging?: (monitor: DragSourceMonitor<DragObject, DropResult>) => boolean;
37
/** Function to collect properties from monitor */
38
collect?: (monitor: DragSourceMonitor<DragObject, DropResult>) => CollectedProps;
39
}
40
41
type DragObjectFactory<T> = (monitor: DragSourceMonitor<T>) => T | null;
42
type FactoryOrInstance<T> = T | (() => T);
43
```
44
45
**Basic Usage:**
46
47
```typescript
48
import React from "react";
49
import { useDrag } from "react-dnd";
50
51
interface DragItem {
52
id: string;
53
name: string;
54
}
55
56
function DraggableCard({ id, name }: DragItem) {
57
const [{ isDragging }, drag] = useDrag({
58
type: "card",
59
item: { id, name },
60
collect: (monitor) => ({
61
isDragging: monitor.isDragging(),
62
}),
63
});
64
65
return (
66
<div
67
ref={drag}
68
style={{
69
opacity: isDragging ? 0.5 : 1,
70
cursor: 'move'
71
}}
72
>
73
{name}
74
</div>
75
);
76
}
77
```
78
79
**Advanced Usage with All Options:**
80
81
```typescript
82
import React, { useState } from "react";
83
import { useDrag } from "react-dnd";
84
85
function AdvancedDraggableItem({ id, data, disabled }) {
86
const [dragCount, setDragCount] = useState(0);
87
88
const [collected, drag, preview] = useDrag({
89
type: "advanced-item",
90
91
// Dynamic item factory
92
item: (monitor) => {
93
console.log("Drag started");
94
return { id, data, timestamp: Date.now() };
95
},
96
97
// Conditional dragging
98
canDrag: !disabled,
99
100
// Custom drag state logic
101
isDragging: (monitor) => {
102
return monitor.getItem()?.id === id;
103
},
104
105
// Drag end handler
106
end: (item, monitor) => {
107
setDragCount(prev => prev + 1);
108
109
if (monitor.didDrop()) {
110
const dropResult = monitor.getDropResult();
111
console.log("Item dropped:", dropResult);
112
} else {
113
console.log("Drag cancelled");
114
}
115
},
116
117
// Visual options
118
options: {
119
dropEffect: "move"
120
},
121
122
// Preview options
123
previewOptions: {
124
captureDraggingState: false,
125
anchorX: 0.5,
126
anchorY: 0.5
127
},
128
129
// Collect function
130
collect: (monitor) => ({
131
isDragging: monitor.isDragging(),
132
canDrag: monitor.canDrag(),
133
itemType: monitor.getItemType(),
134
dragOffset: monitor.getClientOffset(),
135
}),
136
});
137
138
return (
139
<div>
140
<div ref={drag} style={{ opacity: collected.isDragging ? 0.5 : 1 }}>
141
Draggable Item (dragged {dragCount} times)
142
</div>
143
<div ref={preview}>
144
Custom Preview Content
145
</div>
146
</div>
147
);
148
}
149
```
150
151
### Connector Functions
152
153
The useDrag hook returns connector functions for attaching drag functionality to DOM elements.
154
155
```typescript { .api }
156
/** Function to connect DOM elements as drag sources */
157
type ConnectDragSource = DragElementWrapper<DragSourceOptions>;
158
159
/** Function to connect custom drag preview elements */
160
type ConnectDragPreview = DragElementWrapper<DragPreviewOptions>;
161
162
type DragElementWrapper<Options> = (
163
elementOrNode: ConnectableElement,
164
options?: Options
165
) => React.ReactElement | null;
166
167
type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;
168
```
169
170
**Usage Examples:**
171
172
```typescript
173
function CustomConnectorExample() {
174
const [{ isDragging }, drag, preview] = useDrag({
175
type: "item",
176
item: { id: "example" },
177
collect: (monitor) => ({ isDragging: monitor.isDragging() })
178
});
179
180
return (
181
<div>
182
{/* Basic drag connector */}
183
<button ref={drag}>Drag Handle</button>
184
185
{/* Separate preview element */}
186
<div ref={preview}>
187
This will be shown during drag
188
</div>
189
190
{/* Connector with options */}
191
{drag(
192
<div style={{ padding: 10 }}>
193
Custom draggable area
194
</div>,
195
{ dropEffect: "copy" }
196
)}
197
</div>
198
);
199
}
200
```
201
202
### Drag Source Monitor
203
204
Monitor interface providing information about the current drag operation.
205
206
```typescript { .api }
207
interface DragSourceMonitor<DragObject = unknown, DropResult = unknown> {
208
/** Returns true if dragging is allowed */
209
canDrag(): boolean;
210
/** Returns true if this component is being dragged */
211
isDragging(): boolean;
212
/** Returns the type of item being dragged */
213
getItemType(): Identifier | null;
214
/** Returns the dragged item data */
215
getItem<T = DragObject>(): T;
216
/** Returns drop result after drop completes */
217
getDropResult<T = DropResult>(): T | null;
218
/** Returns true if drop was handled by a target */
219
didDrop(): boolean;
220
/** Returns initial pointer coordinates when drag started */
221
getInitialClientOffset(): XYCoord | null;
222
/** Returns initial drag source coordinates */
223
getInitialSourceClientOffset(): XYCoord | null;
224
/** Returns current pointer coordinates */
225
getClientOffset(): XYCoord | null;
226
/** Returns pointer movement since drag start */
227
getDifferenceFromInitialOffset(): XYCoord | null;
228
/** Returns projected source coordinates */
229
getSourceClientOffset(): XYCoord | null;
230
/** Returns IDs of potential drop targets */
231
getTargetIds(): Identifier[];
232
}
233
```
234
235
## Configuration Options
236
237
### Drag Source Options
238
239
```typescript { .api }
240
interface DragSourceOptions {
241
/** Visual drop effect hint ('move', 'copy', etc.) */
242
dropEffect?: string;
243
}
244
```
245
246
### Drag Preview Options
247
248
```typescript { .api }
249
interface DragPreviewOptions {
250
/** Whether to immediately capture dragging state */
251
captureDraggingState?: boolean;
252
/** Horizontal anchor point (0-1) */
253
anchorX?: number;
254
/** Vertical anchor point (0-1) */
255
anchorY?: number;
256
/** Horizontal offset from cursor */
257
offsetX?: number;
258
/** Vertical offset from cursor */
259
offsetY?: number;
260
}
261
```
262
263
## Common Patterns
264
265
### Conditional Dragging
266
267
```typescript
268
function ConditionalDrag({ item, canEdit }) {
269
const [{ isDragging }, drag] = useDrag({
270
type: "item",
271
item,
272
canDrag: canEdit && item.status !== "locked",
273
collect: (monitor) => ({
274
isDragging: monitor.isDragging(),
275
}),
276
});
277
278
return (
279
<div
280
ref={canEdit ? drag : null}
281
style={{
282
opacity: isDragging ? 0.5 : 1,
283
cursor: canEdit ? 'move' : 'default'
284
}}
285
>
286
{item.name}
287
</div>
288
);
289
}
290
```
291
292
### Multi-type Dragging
293
294
```typescript
295
function MultiTypeDragItem({ item, mode }) {
296
const [{ isDragging }, drag] = useDrag({
297
type: mode === "copy" ? "copyable-item" : "movable-item",
298
item: { ...item, mode },
299
collect: (monitor) => ({
300
isDragging: monitor.isDragging(),
301
}),
302
});
303
304
return <div ref={drag}>{item.name}</div>;
305
}
306
```