0
# Utilities
1
2
React DnD provides utility components and functions for advanced drag and drop scenarios and integration with the underlying drag and drop system.
3
4
## Capabilities
5
6
### DragPreviewImage
7
8
Utility component for rendering drag preview images with proper lifecycle management.
9
10
```typescript { .api }
11
/**
12
* A utility component for rendering a drag preview image
13
* @param props - Preview image configuration
14
* @returns React element (renders null, used for side effects)
15
*/
16
function DragPreviewImage(props: DragPreviewImageProps): React.ReactElement;
17
18
interface DragPreviewImageProps {
19
/** Connector function from useDrag hook */
20
connect: ConnectDragPreview;
21
/** Image source URL */
22
src: string;
23
}
24
```
25
26
**Usage Example:**
27
28
```typescript
29
import React from "react";
30
import { useDrag, DragPreviewImage } from "react-dnd";
31
32
function DraggableWithImagePreview({ id, name, previewImageUrl }) {
33
const [{ isDragging }, drag, preview] = useDrag({
34
type: "item",
35
item: { id, name },
36
collect: (monitor) => ({
37
isDragging: monitor.isDragging(),
38
}),
39
});
40
41
return (
42
<div>
43
{/* The DragPreviewImage component handles image loading and connection */}
44
<DragPreviewImage connect={preview} src={previewImageUrl} />
45
46
<div
47
ref={drag}
48
style={{ opacity: isDragging ? 0.5 : 1 }}
49
>
50
{name}
51
</div>
52
</div>
53
);
54
}
55
```
56
57
**Advanced Usage with Dynamic Images:**
58
59
```typescript
60
import React, { useState, useEffect } from "react";
61
import { useDrag, DragPreviewImage } from "react-dnd";
62
63
function DynamicPreviewItem({ item }) {
64
const [previewUrl, setPreviewUrl] = useState(null);
65
66
const [{ isDragging }, drag, preview] = useDrag({
67
type: "item",
68
item,
69
collect: (monitor) => ({
70
isDragging: monitor.isDragging(),
71
}),
72
});
73
74
// Generate or fetch preview image
75
useEffect(() => {
76
generatePreviewImage(item).then(setPreviewUrl);
77
}, [item]);
78
79
return (
80
<div>
81
{previewUrl && <DragPreviewImage connect={preview} src={previewUrl} />}
82
83
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
84
<ItemContent item={item} />
85
</div>
86
</div>
87
);
88
}
89
90
async function generatePreviewImage(item) {
91
// Generate canvas-based preview or fetch from API
92
const canvas = document.createElement("canvas");
93
const ctx = canvas.getContext("2d");
94
95
canvas.width = 200;
96
canvas.height = 100;
97
98
ctx.fillStyle = "#f0f0f0";
99
ctx.fillRect(0, 0, canvas.width, canvas.height);
100
ctx.fillStyle = "#333";
101
ctx.font = "16px Arial";
102
ctx.fillText(item.name, 10, 50);
103
104
return canvas.toDataURL();
105
}
106
```
107
108
### useDragDropManager Hook
109
110
Low-level hook for accessing the DragDropManager instance directly for advanced operations.
111
112
```typescript { .api }
113
/**
114
* Hook to retrieve the DragDropManager from Context
115
* @returns DragDropManager instance
116
* @throws Error if used outside DndProvider
117
*/
118
function useDragDropManager(): DragDropManager;
119
```
120
121
**Usage Examples:**
122
123
```typescript
124
import React, { useEffect, useState } from "react";
125
import { useDragDropManager } from "react-dnd";
126
127
function AdvancedDragMonitor() {
128
const manager = useDragDropManager();
129
const [dragState, setDragState] = useState({});
130
131
useEffect(() => {
132
const monitor = manager.getMonitor();
133
134
// Subscribe to all drag and drop state changes
135
const unsubscribe = monitor.subscribeToStateChange(() => {
136
setDragState({
137
isDragging: monitor.isDragging(),
138
itemType: monitor.getItemType(),
139
item: monitor.getItem(),
140
dropResult: monitor.getDropResult(),
141
clientOffset: monitor.getClientOffset(),
142
});
143
});
144
145
return unsubscribe;
146
}, [manager]);
147
148
return (
149
<div className="drag-monitor">
150
<h3>Global Drag State</h3>
151
<pre>{JSON.stringify(dragState, null, 2)}</pre>
152
</div>
153
);
154
}
155
```
156
157
**Backend Management:**
158
159
```typescript
160
import React, { useEffect } from "react";
161
import { useDragDropManager } from "react-dnd";
162
163
function BackendController() {
164
const manager = useDragDropManager();
165
166
useEffect(() => {
167
const backend = manager.getBackend();
168
169
// Access backend-specific functionality
170
if (backend.setup) {
171
backend.setup();
172
}
173
174
// Custom backend configuration
175
if (backend.addEventListener) {
176
backend.addEventListener("dragstart", handleDragStart);
177
backend.addEventListener("dragend", handleDragEnd);
178
}
179
180
return () => {
181
if (backend.teardown) {
182
backend.teardown();
183
}
184
};
185
}, [manager]);
186
187
const handleDragStart = (event) => {
188
console.log("Backend drag start:", event);
189
};
190
191
const handleDragEnd = (event) => {
192
console.log("Backend drag end:", event);
193
};
194
195
return <div>Backend Controller Active</div>;
196
}
197
```
198
199
### Type Guards and Utilities
200
201
Utility functions for working with React DnD types and values.
202
203
```typescript { .api }
204
/** Utility type for values that can be provided as instance or factory */
205
type FactoryOrInstance<T> = T | (() => T);
206
207
/** Factory function type for creating drag objects */
208
type DragObjectFactory<T> = (monitor: DragSourceMonitor<T>) => T | null;
209
210
/** Coordinate pair for pointer/element positions */
211
interface XYCoord {
212
x: number;
213
y: number;
214
}
215
216
/** Valid element types that can be connected for drag/drop */
217
type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;
218
```
219
220
**Utility Functions Example:**
221
222
```typescript
223
import React from "react";
224
import type { XYCoord, ConnectableElement } from "react-dnd";
225
226
// Helper function to calculate distance between coordinates
227
function calculateDistance(a: XYCoord, b: XYCoord): number {
228
return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
229
}
230
231
// Helper function to check if element is connectable
232
function isConnectable(element: any): element is ConnectableElement {
233
return element === null ||
234
React.isValidElement(element) ||
235
(element && typeof element === "object" && "current" in element) ||
236
element instanceof Element;
237
}
238
239
// Helper to create factory functions
240
function createItemFactory<T>(baseItem: T) {
241
return (monitor: DragSourceMonitor<T>): T => {
242
return {
243
...baseItem,
244
dragStartTime: Date.now(),
245
initialOffset: monitor.getInitialClientOffset(),
246
};
247
};
248
}
249
250
function UtilityExample() {
251
const itemFactory = createItemFactory({ id: "1", name: "Example" });
252
253
const [{ isDragging }, drag] = useDrag({
254
type: "item",
255
item: itemFactory, // Use factory function
256
collect: (monitor) => ({
257
isDragging: monitor.isDragging(),
258
}),
259
});
260
261
return <div ref={drag}>Draggable with factory</div>;
262
}
263
```
264
265
## Integration Helpers
266
267
### Custom Backend Integration
268
269
```typescript
270
import React from "react";
271
import { DndProvider, useDragDropManager } from "react-dnd";
272
273
// Custom backend example
274
class CustomBackend {
275
constructor(manager, context, options) {
276
this.manager = manager;
277
this.context = context;
278
this.options = options;
279
}
280
281
setup() {
282
// Initialize custom drag and drop handling
283
this.context.addEventListener("mousedown", this.handleMouseDown);
284
this.context.addEventListener("mouseup", this.handleMouseUp);
285
}
286
287
teardown() {
288
// Clean up event listeners
289
this.context.removeEventListener("mousedown", this.handleMouseDown);
290
this.context.removeEventListener("mouseup", this.handleMouseUp);
291
}
292
293
handleMouseDown = (event) => {
294
// Custom drag initiation logic
295
};
296
297
handleMouseUp = (event) => {
298
// Custom drop handling logic
299
};
300
}
301
302
function createCustomBackend(manager, context, options) {
303
return new CustomBackend(manager, context, options);
304
}
305
306
function AppWithCustomBackend() {
307
return (
308
<DndProvider backend={createCustomBackend}>
309
<MyDragDropComponents />
310
</DndProvider>
311
);
312
}
313
```
314
315
### Testing Utilities
316
317
```typescript
318
import React from "react";
319
import { render, fireEvent } from "@testing-library/react";
320
import { DndProvider } from "react-dnd";
321
import { TestBackend } from "react-dnd-test-backend";
322
323
function createDndWrapper() {
324
return function DndWrapper({ children }) {
325
return (
326
<DndProvider backend={TestBackend}>
327
{children}
328
</DndProvider>
329
);
330
};
331
}
332
333
// Example test utility
334
function simulateDragDrop(dragElement, dropElement) {
335
fireEvent.dragStart(dragElement);
336
fireEvent.dragEnter(dropElement);
337
fireEvent.dragOver(dropElement);
338
fireEvent.drop(dropElement);
339
fireEvent.dragEnd(dragElement);
340
}
341
342
// Test example
343
test("drag and drop functionality", () => {
344
const { getByTestId } = render(
345
<MyDragDropComponent />,
346
{ wrapper: createDndWrapper() }
347
);
348
349
const dragElement = getByTestId("draggable");
350
const dropElement = getByTestId("droppable");
351
352
simulateDragDrop(dragElement, dropElement);
353
354
// Assert expected behavior
355
});
356
```
357
358
### Performance Optimization Helpers
359
360
```typescript
361
import React, { useMemo, useCallback } from "react";
362
import { useDrag, useDrop } from "react-dnd";
363
364
// Memoized drag spec to prevent unnecessary re-renders
365
function OptimizedDraggable({ item, onDragEnd }) {
366
const dragSpec = useMemo(() => ({
367
type: "item",
368
item,
369
end: onDragEnd,
370
collect: (monitor) => ({
371
isDragging: monitor.isDragging(),
372
}),
373
}), [item, onDragEnd]);
374
375
const [{ isDragging }, drag] = useDrag(dragSpec);
376
377
return (
378
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
379
{item.name}
380
</div>
381
);
382
}
383
384
// Optimized drop target with memoized handlers
385
function OptimizedDropTarget({ onDrop, acceptTypes }) {
386
const handleDrop = useCallback((item, monitor) => {
387
onDrop(item, monitor.getDropResult());
388
}, [onDrop]);
389
390
const dropSpec = useMemo(() => ({
391
accept: acceptTypes,
392
drop: handleDrop,
393
collect: (monitor) => ({
394
isOver: monitor.isOver(),
395
canDrop: monitor.canDrop(),
396
}),
397
}), [acceptTypes, handleDrop]);
398
399
const [{ isOver, canDrop }, drop] = useDrop(dropSpec);
400
401
return (
402
<div
403
ref={drop}
404
style={{
405
backgroundColor: isOver && canDrop ? "lightgreen" : "transparent",
406
}}
407
>
408
Drop Zone
409
</div>
410
);
411
}
412
```