0
# Drag Layer
1
2
The useDragLayer hook enables access to global drag state for creating custom drag previews and drag-aware components that respond to drag operations anywhere in the application.
3
4
## Capabilities
5
6
### useDragLayer Hook
7
8
Hook for accessing global drag state and creating custom drag layer components.
9
10
```typescript { .api }
11
/**
12
* Hook for accessing drag layer state for custom drag previews
13
* @param collect - Function to collect properties from the drag layer monitor
14
* @returns Collected properties from the monitor
15
*/
16
function useDragLayer<CollectedProps, DragObject = any>(
17
collect: (monitor: DragLayerMonitor<DragObject>) => CollectedProps
18
): CollectedProps;
19
```
20
21
**Basic Usage:**
22
23
```typescript
24
import React from "react";
25
import { useDragLayer } from "react-dnd";
26
27
function CustomDragLayer() {
28
const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({
29
isDragging: monitor.isDragging(),
30
itemType: monitor.getItemType(),
31
item: monitor.getItem(),
32
currentOffset: monitor.getClientOffset(),
33
}));
34
35
if (!isDragging || !currentOffset) {
36
return null;
37
}
38
39
return (
40
<div
41
style={{
42
position: "fixed",
43
pointerEvents: "none",
44
zIndex: 100,
45
left: currentOffset.x,
46
top: currentOffset.y,
47
transform: "translate(-50%, -50%)",
48
}}
49
>
50
<CustomPreview itemType={itemType} item={item} />
51
</div>
52
);
53
}
54
55
function CustomPreview({ itemType, item }) {
56
switch (itemType) {
57
case "card":
58
return <div className="card-preview">{item.name}</div>;
59
case "file":
60
return <div className="file-preview">📄 {item.filename}</div>;
61
default:
62
return <div className="default-preview">Dragging...</div>;
63
}
64
}
65
```
66
67
**Advanced Drag Layer with Animations:**
68
69
```typescript
70
import React, { useState, useEffect } from "react";
71
import { useDragLayer } from "react-dnd";
72
73
function AnimatedDragLayer() {
74
const [trail, setTrail] = useState([]);
75
76
const {
77
isDragging,
78
itemType,
79
item,
80
currentOffset,
81
initialOffset,
82
differenceFromInitialOffset,
83
} = useDragLayer((monitor) => ({
84
isDragging: monitor.isDragging(),
85
itemType: monitor.getItemType(),
86
item: monitor.getItem(),
87
currentOffset: monitor.getClientOffset(),
88
initialOffset: monitor.getInitialClientOffset(),
89
differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(),
90
}));
91
92
// Create trail effect
93
useEffect(() => {
94
if (isDragging && currentOffset) {
95
setTrail(prev => [
96
...prev.slice(-10), // Keep last 10 positions
97
{ x: currentOffset.x, y: currentOffset.y, timestamp: Date.now() }
98
]);
99
} else {
100
setTrail([]);
101
}
102
}, [isDragging, currentOffset]);
103
104
if (!isDragging) {
105
return null;
106
}
107
108
const layerStyles = {
109
position: "fixed" as const,
110
pointerEvents: "none" as const,
111
zIndex: 100,
112
left: 0,
113
top: 0,
114
width: "100%",
115
height: "100%",
116
};
117
118
return (
119
<div style={layerStyles}>
120
{/* Trail effect */}
121
{trail.map((point, index) => (
122
<div
123
key={point.timestamp}
124
style={{
125
position: "absolute",
126
left: point.x,
127
top: point.y,
128
width: 4,
129
height: 4,
130
backgroundColor: "rgba(0, 100, 255, " + (index / trail.length) + ")",
131
borderRadius: "50%",
132
transform: "translate(-50%, -50%)",
133
}}
134
/>
135
))}
136
137
{/* Main preview */}
138
{currentOffset && (
139
<div
140
style={{
141
position: "absolute",
142
left: currentOffset.x,
143
top: currentOffset.y,
144
transform: "translate(-50%, -50%) rotate(" +
145
(differenceFromInitialOffset ? differenceFromInitialOffset.x * 0.1 : 0) + "deg)",
146
transition: "transform 0.1s ease-out",
147
}}
148
>
149
<DragPreview itemType={itemType} item={item} />
150
</div>
151
)}
152
</div>
153
);
154
}
155
```
156
157
### Drag Layer Monitor
158
159
Monitor interface providing global drag state information.
160
161
```typescript { .api }
162
interface DragLayerMonitor<DragObject = unknown> {
163
/** Returns true if any drag operation is in progress */
164
isDragging(): boolean;
165
/** Returns the type of item being dragged */
166
getItemType(): Identifier | null;
167
/** Returns the dragged item data */
168
getItem<T = DragObject>(): T;
169
/** Returns initial pointer coordinates when drag started */
170
getInitialClientOffset(): XYCoord | null;
171
/** Returns initial drag source coordinates */
172
getInitialSourceClientOffset(): XYCoord | null;
173
/** Returns current pointer coordinates */
174
getClientOffset(): XYCoord | null;
175
/** Returns pointer movement since drag start */
176
getDifferenceFromInitialOffset(): XYCoord | null;
177
/** Returns projected source coordinates */
178
getSourceClientOffset(): XYCoord | null;
179
}
180
```
181
182
## Common Patterns
183
184
### Multi-type Custom Previews
185
186
```typescript
187
function TypeAwareDragLayer() {
188
const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({
189
isDragging: monitor.isDragging(),
190
itemType: monitor.getItemType(),
191
item: monitor.getItem(),
192
currentOffset: monitor.getClientOffset(),
193
}));
194
195
const renderPreview = () => {
196
switch (itemType) {
197
case "card":
198
return (
199
<div className="card-drag-preview">
200
<h4>{item.title}</h4>
201
<p>{item.content}</p>
202
</div>
203
);
204
205
case "file":
206
return (
207
<div className="file-drag-preview">
208
<span className="file-icon">{getFileIcon(item.type)}</span>
209
<span className="file-name">{item.name}</span>
210
<span className="file-size">{formatSize(item.size)}</span>
211
</div>
212
);
213
214
case "list-item":
215
return (
216
<div className="list-item-preview">
217
<div className="item-count">{item.items?.length || 1} item(s)</div>
218
<div className="item-title">{item.title}</div>
219
</div>
220
);
221
222
default:
223
return <div className="default-preview">Dragging {itemType}</div>;
224
}
225
};
226
227
if (!isDragging || !currentOffset) {
228
return null;
229
}
230
231
return (
232
<div
233
style={{
234
position: "fixed",
235
pointerEvents: "none",
236
zIndex: 1000,
237
left: currentOffset.x,
238
top: currentOffset.y,
239
transform: "translate(-50%, -50%)",
240
}}
241
>
242
{renderPreview()}
243
</div>
244
);
245
}
246
```
247
248
### Responsive Drag Feedback
249
250
```typescript
251
function ResponsiveDragLayer() {
252
const {
253
isDragging,
254
item,
255
currentOffset,
256
differenceFromInitialOffset,
257
} = useDragLayer((monitor) => ({
258
isDragging: monitor.isDragging(),
259
item: monitor.getItem(),
260
currentOffset: monitor.getClientOffset(),
261
differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(),
262
}));
263
264
if (!isDragging || !currentOffset || !differenceFromInitialOffset) {
265
return null;
266
}
267
268
// Calculate drag velocity and direction
269
const velocity = Math.sqrt(
270
Math.pow(differenceFromInitialOffset.x, 2) +
271
Math.pow(differenceFromInitialOffset.y, 2)
272
);
273
274
const scale = Math.min(1.2, 1 + velocity * 0.001);
275
const rotation = differenceFromInitialOffset.x * 0.05;
276
277
return (
278
<div
279
style={{
280
position: "fixed",
281
pointerEvents: "none",
282
zIndex: 100,
283
left: currentOffset.x,
284
top: currentOffset.y,
285
transform: `translate(-50%, -50%) scale(${scale}) rotate(${rotation}deg)`,
286
transition: "transform 0.1s ease-out",
287
}}
288
>
289
<div className="responsive-preview">
290
{item.name}
291
<div className="velocity-indicator" style={{ opacity: velocity * 0.01 }}>
292
Fast!
293
</div>
294
</div>
295
</div>
296
);
297
}
298
```
299
300
### Drag State Indicator
301
302
```typescript
303
function DragStateIndicator() {
304
const { isDragging, itemType, currentOffset } = useDragLayer((monitor) => ({
305
isDragging: monitor.isDragging(),
306
itemType: monitor.getItemType(),
307
currentOffset: monitor.getClientOffset(),
308
}));
309
310
return (
311
<div className="drag-state-indicator">
312
<div className={`status ${isDragging ? "active" : "inactive"}`}>
313
{isDragging ? `Dragging ${itemType}` : "No active drag"}
314
</div>
315
316
{isDragging && currentOffset && (
317
<div className="coordinates">
318
Position: {Math.round(currentOffset.x)}, {Math.round(currentOffset.y)}
319
</div>
320
)}
321
</div>
322
);
323
}
324
```
325
326
### Drag Constraints Visualization
327
328
```typescript
329
function ConstrainedDragLayer({ bounds }) {
330
const { isDragging, currentOffset, item } = useDragLayer((monitor) => ({
331
isDragging: monitor.isDragging(),
332
currentOffset: monitor.getClientOffset(),
333
item: monitor.getItem(),
334
}));
335
336
if (!isDragging || !currentOffset) {
337
return null;
338
}
339
340
// Check if drag is within allowed bounds
341
const isWithinBounds = bounds &&
342
currentOffset.x >= bounds.left &&
343
currentOffset.x <= bounds.right &&
344
currentOffset.y >= bounds.top &&
345
currentOffset.y <= bounds.bottom;
346
347
return (
348
<div
349
style={{
350
position: "fixed",
351
pointerEvents: "none",
352
zIndex: 100,
353
left: currentOffset.x,
354
top: currentOffset.y,
355
transform: "translate(-50%, -50%)",
356
}}
357
>
358
<div
359
className={`constrained-preview ${isWithinBounds ? "valid" : "invalid"}`}
360
style={{
361
border: isWithinBounds ? "2px solid green" : "2px solid red",
362
backgroundColor: isWithinBounds ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)",
363
}}
364
>
365
{item.name}
366
{!isWithinBounds && <div className="warning">⚠️ Outside bounds</div>}
367
</div>
368
</div>
369
);
370
}
371
```
372
373
### Global Drag Layer Portal
374
375
```typescript
376
import React from "react";
377
import { createPortal } from "react-dom";
378
import { useDragLayer } from "react-dnd";
379
380
function GlobalDragLayerPortal() {
381
const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({
382
isDragging: monitor.isDragging(),
383
itemType: monitor.getItemType(),
384
item: monitor.getItem(),
385
currentOffset: monitor.getClientOffset(),
386
}));
387
388
if (!isDragging || !currentOffset) {
389
return null;
390
}
391
392
const dragLayer = (
393
<div
394
style={{
395
position: "fixed",
396
pointerEvents: "none",
397
zIndex: 9999,
398
left: currentOffset.x,
399
top: currentOffset.y,
400
transform: "translate(-50%, -50%)",
401
}}
402
>
403
<GlobalDragPreview itemType={itemType} item={item} />
404
</div>
405
);
406
407
// Render to body to ensure it's always on top
408
return createPortal(dragLayer, document.body);
409
}
410
411
function GlobalDragPreview({ itemType, item }) {
412
return (
413
<div className={`global-drag-preview ${itemType}`}>
414
<div className="preview-content">
415
{item.name || item.title || "Dragging..."}
416
</div>
417
<div className="preview-shadow" />
418
</div>
419
);
420
}
421
```