0
# Drop Targets
1
2
The useDrop hook enables components to act as drop targets, accepting dragged items and handling drop operations.
3
4
## Capabilities
5
6
### useDrop Hook
7
8
Creates a drop target that can accept dragged items with flexible handling options.
9
10
```typescript { .api }
11
/**
12
* Hook for making components accept dropped items
13
* @param specArg - Drop target specification (object or function)
14
* @param deps - Optional dependency array for memoization
15
* @returns Tuple of [collected props, drop ref connector]
16
*/
17
function useDrop<DragObject = unknown, DropResult = unknown, CollectedProps = unknown>(
18
specArg: FactoryOrInstance<DropTargetHookSpec<DragObject, DropResult, CollectedProps>>,
19
deps?: unknown[]
20
): [CollectedProps, ConnectDropTarget];
21
22
interface DropTargetHookSpec<DragObject, DropResult, CollectedProps> {
23
/** The kinds of drag items this drop target accepts */
24
accept: TargetType;
25
/** Drop target options */
26
options?: DropTargetOptions;
27
/** Called when a compatible item is dropped */
28
drop?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => DropResult | undefined;
29
/** Called when an item is hovered over the component */
30
hover?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => void;
31
/** Determines whether the drop target can accept the item */
32
canDrop?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => boolean;
33
/** Function to collect properties from monitor */
34
collect?: (monitor: DropTargetMonitor<DragObject, DropResult>) => CollectedProps;
35
}
36
37
type FactoryOrInstance<T> = T | (() => T);
38
```
39
40
**Basic Usage:**
41
42
```typescript
43
import React, { useState } from "react";
44
import { useDrop } from "react-dnd";
45
46
interface DropItem {
47
id: string;
48
name: string;
49
}
50
51
function DropZone() {
52
const [droppedItems, setDroppedItems] = useState<DropItem[]>([]);
53
54
const [{ isOver, canDrop }, drop] = useDrop({
55
accept: "card",
56
drop: (item: DropItem) => {
57
setDroppedItems(prev => [...prev, item]);
58
return { dropped: true };
59
},
60
collect: (monitor) => ({
61
isOver: monitor.isOver(),
62
canDrop: monitor.canDrop(),
63
}),
64
});
65
66
return (
67
<div
68
ref={drop}
69
style={{
70
backgroundColor: isOver && canDrop ? "lightgreen" : "lightgray",
71
minHeight: 200,
72
padding: 16
73
}}
74
>
75
{canDrop ? "Drop items here!" : "Drag compatible items here"}
76
{droppedItems.map(item => (
77
<div key={item.id}>{item.name}</div>
78
))}
79
</div>
80
);
81
}
82
```
83
84
**Advanced Usage with All Options:**
85
86
```typescript
87
import React, { useState, useRef } from "react";
88
import { useDrop } from "react-dnd";
89
90
function AdvancedDropTarget({ onItemMoved, acceptedTypes }) {
91
const [dropHistory, setDropHistory] = useState([]);
92
const dropRef = useRef(null);
93
94
const [collected, drop] = useDrop({
95
// Accept multiple types
96
accept: acceptedTypes,
97
98
// Conditional drop acceptance
99
canDrop: (item, monitor) => {
100
return item.status !== "locked" && item.id !== "restricted";
101
},
102
103
// Hover handler for drag feedback
104
hover: (item, monitor) => {
105
if (!dropRef.current) return;
106
107
const dragIndex = item.index;
108
const hoverIndex = findHoverIndex(monitor.getClientOffset());
109
110
if (dragIndex !== hoverIndex) {
111
// Provide visual feedback during hover
112
highlightDropPosition(hoverIndex);
113
}
114
},
115
116
// Drop handler with detailed result
117
drop: (item, monitor) => {
118
const dropOffset = monitor.getClientOffset();
119
const dropResult = {
120
item,
121
position: calculateDropPosition(dropOffset),
122
timestamp: Date.now(),
123
isExactDrop: monitor.isOver({ shallow: true })
124
};
125
126
setDropHistory(prev => [...prev, dropResult]);
127
onItemMoved?.(item, dropResult.position);
128
129
return dropResult;
130
},
131
132
// Comprehensive collect function
133
collect: (monitor) => ({
134
isOver: monitor.isOver(),
135
isOverShallow: monitor.isOver({ shallow: true }),
136
canDrop: monitor.canDrop(),
137
itemType: monitor.getItemType(),
138
draggedItem: monitor.getItem(),
139
dropResult: monitor.getDropResult(),
140
didDrop: monitor.didDrop(),
141
}),
142
});
143
144
return (
145
<div
146
ref={(node) => {
147
drop(node);
148
dropRef.current = node;
149
}}
150
style={{
151
backgroundColor: collected.isOver && collected.canDrop ? "lightblue" : "white",
152
border: collected.canDrop ? "2px dashed blue" : "1px solid gray",
153
minHeight: 300,
154
position: "relative"
155
}}
156
>
157
<div>Drop Target - {collected.itemType || "No item"}</div>
158
{collected.isOver && !collected.canDrop && (
159
<div style={{ color: "red" }}>Cannot drop this item here</div>
160
)}
161
{dropHistory.length > 0 && (
162
<div>
163
<h4>Drop History:</h4>
164
{dropHistory.map((drop, index) => (
165
<div key={index}>{drop.item.name} at {drop.timestamp}</div>
166
))}
167
</div>
168
)}
169
</div>
170
);
171
}
172
```
173
174
### Drop Target Connector
175
176
The useDrop hook returns a connector function for attaching drop functionality to DOM elements.
177
178
```typescript { .api }
179
/** Function to connect DOM elements as drop targets */
180
type ConnectDropTarget = DragElementWrapper<any>;
181
182
type DragElementWrapper<Options> = (
183
elementOrNode: ConnectableElement,
184
options?: Options
185
) => React.ReactElement | null;
186
187
type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;
188
```
189
190
**Usage Examples:**
191
192
```typescript
193
function CustomConnectorExample() {
194
const [{ isOver }, drop] = useDrop({
195
accept: "item",
196
collect: (monitor) => ({ isOver: monitor.isOver() })
197
});
198
199
return (
200
<div>
201
{/* Basic drop connector */}
202
<div ref={drop}>Basic drop area</div>
203
204
{/* Connector with JSX element */}
205
{drop(
206
<div style={{
207
border: isOver ? "2px solid blue" : "1px dashed gray",
208
padding: 20
209
}}>
210
Custom drop area
211
</div>
212
)}
213
</div>
214
);
215
}
216
```
217
218
### Drop Target Monitor
219
220
Monitor interface providing information about the current drop operation.
221
222
```typescript { .api }
223
interface DropTargetMonitor<DragObject = unknown, DropResult = unknown> {
224
/** Returns true if drop is allowed */
225
canDrop(): boolean;
226
/** Returns true if pointer is over this target */
227
isOver(options?: { shallow?: boolean }): boolean;
228
/** Returns the type of item being dragged */
229
getItemType(): Identifier | null;
230
/** Returns the dragged item data */
231
getItem<T = DragObject>(): T;
232
/** Returns drop result after drop completes */
233
getDropResult<T = DropResult>(): T | null;
234
/** Returns true if drop was handled */
235
didDrop(): boolean;
236
/** Returns initial pointer coordinates when drag started */
237
getInitialClientOffset(): XYCoord | null;
238
/** Returns initial drag source coordinates */
239
getInitialSourceClientOffset(): XYCoord | null;
240
/** Returns current pointer coordinates */
241
getClientOffset(): XYCoord | null;
242
/** Returns pointer movement since drag start */
243
getDifferenceFromInitialOffset(): XYCoord | null;
244
/** Returns projected source coordinates */
245
getSourceClientOffset(): XYCoord | null;
246
}
247
```
248
249
## Configuration Options
250
251
### Drop Target Options
252
253
```typescript { .api }
254
/** Flexible options object for drop target configuration */
255
type DropTargetOptions = any;
256
```
257
258
## Common Patterns
259
260
### Multiple Item Types
261
262
```typescript
263
function MultiTypeDropTarget() {
264
const [{ isOver, itemType }, drop] = useDrop({
265
accept: ["card", "item", "file"],
266
drop: (item, monitor) => {
267
const type = monitor.getItemType();
268
269
switch (type) {
270
case "card":
271
handleCardDrop(item);
272
break;
273
case "item":
274
handleItemDrop(item);
275
break;
276
case "file":
277
handleFileDrop(item);
278
break;
279
}
280
281
return { acceptedType: type };
282
},
283
collect: (monitor) => ({
284
isOver: monitor.isOver(),
285
itemType: monitor.getItemType(),
286
}),
287
});
288
289
return (
290
<div ref={drop}>
291
{isOver && <div>Dropping {itemType}...</div>}
292
Multi-type drop zone
293
</div>
294
);
295
}
296
```
297
298
### Nested Drop Targets
299
300
```typescript
301
function NestedDropTargets() {
302
const [{ isOverOuter }, dropOuter] = useDrop({
303
accept: "item",
304
drop: (item, monitor) => {
305
// Only handle if not dropped on inner target
306
if (!monitor.didDrop()) {
307
return { droppedOn: "outer" };
308
}
309
},
310
collect: (monitor) => ({
311
isOverOuter: monitor.isOver({ shallow: true }),
312
}),
313
});
314
315
const [{ isOverInner }, dropInner] = useDrop({
316
accept: "item",
317
drop: (item) => {
318
return { droppedOn: "inner" };
319
},
320
collect: (monitor) => ({
321
isOverInner: monitor.isOver(),
322
}),
323
});
324
325
return (
326
<div ref={dropOuter} style={{ padding: 20, border: "1px solid blue" }}>
327
Outer Target {isOverOuter && "(hovering outer)"}
328
<div ref={dropInner} style={{ padding: 20, border: "1px solid red" }}>
329
Inner Target {isOverInner && "(hovering inner)"}
330
</div>
331
</div>
332
);
333
}
334
```
335
336
### Conditional Dropping
337
338
```typescript
339
function ConditionalDropTarget({ isEnabled, maxItems, currentItems }) {
340
const [{ isOver, canDrop }, drop] = useDrop({
341
accept: "item",
342
canDrop: (item) => {
343
return isEnabled &&
344
currentItems.length < maxItems &&
345
!currentItems.find(existing => existing.id === item.id);
346
},
347
drop: (item) => {
348
return { accepted: true, timestamp: Date.now() };
349
},
350
collect: (monitor) => ({
351
isOver: monitor.isOver(),
352
canDrop: monitor.canDrop(),
353
}),
354
});
355
356
return (
357
<div
358
ref={drop}
359
style={{
360
backgroundColor: isOver && canDrop ? "lightgreen" :
361
isOver && !canDrop ? "lightcoral" : "white",
362
}}
363
>
364
{!isEnabled && "Drop target disabled"}
365
{isEnabled && currentItems.length >= maxItems && "Maximum items reached"}
366
{isEnabled && currentItems.length < maxItems && "Ready to accept items"}
367
</div>
368
);
369
}
370
```
371
372
### Position-aware Dropping
373
374
```typescript
375
function PositionAwareDropTarget() {
376
const [items, setItems] = useState([]);
377
378
const [{ isOver }, drop] = useDrop({
379
accept: "item",
380
hover: (item, monitor) => {
381
if (!ref.current) return;
382
383
const clientOffset = monitor.getClientOffset();
384
const targetBounds = ref.current.getBoundingClientRect();
385
386
// Calculate relative position within drop target
387
const relativeX = (clientOffset.x - targetBounds.left) / targetBounds.width;
388
const relativeY = (clientOffset.y - targetBounds.top) / targetBounds.height;
389
390
// Provide visual feedback based on position
391
updateDropIndicator(relativeX, relativeY);
392
},
393
drop: (item, monitor) => {
394
const clientOffset = monitor.getClientOffset();
395
const targetBounds = ref.current.getBoundingClientRect();
396
397
const position = {
398
x: clientOffset.x - targetBounds.left,
399
y: clientOffset.y - targetBounds.top,
400
};
401
402
setItems(prev => [...prev, { ...item, position }]);
403
return { position };
404
},
405
collect: (monitor) => ({
406
isOver: monitor.isOver(),
407
}),
408
});
409
410
const ref = useRef(null);
411
412
return (
413
<div
414
ref={(node) => {
415
drop(node);
416
ref.current = node;
417
}}
418
style={{ position: "relative", width: 400, height: 300, border: "1px solid black" }}
419
>
420
{items.map((item, index) => (
421
<div
422
key={index}
423
style={{
424
position: "absolute",
425
left: item.position.x,
426
top: item.position.y,
427
padding: 4,
428
backgroundColor: "lightblue",
429
}}
430
>
431
{item.name}
432
</div>
433
))}
434
</div>
435
);
436
}
437
```