Drag and Drop for React applications with hooks-based API and TypeScript support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The useDrag hook enables components to act as drag sources, allowing users to drag elements and initiate drag and drop operations.
Creates a draggable element with comprehensive configuration options for drag behavior.
/**
* Hook for making components draggable
* @param specArg - Drag source specification (object or function)
* @param deps - Optional dependency array for memoization
* @returns Tuple of [collected props, drag ref connector, preview ref connector]
*/
function useDrag<DragObject = unknown, DropResult = unknown, CollectedProps = unknown>(
specArg: FactoryOrInstance<DragSourceHookSpec<DragObject, DropResult, CollectedProps>>,
deps?: unknown[]
): [CollectedProps, ConnectDragSource, ConnectDragPreview];
interface DragSourceHookSpec<DragObject, DropResult, CollectedProps> {
/** The type of item being dragged - required */
type: SourceType;
/** Item data or factory function - defines what data is available to drop targets */
item?: DragObject | DragObjectFactory<DragObject>;
/** Drag source options for visual effects */
options?: DragSourceOptions;
/** Preview rendering options */
previewOptions?: DragPreviewOptions;
/** Called when drag operation ends */
end?: (draggedItem: DragObject, monitor: DragSourceMonitor<DragObject, DropResult>) => void;
/** Determines if dragging is allowed */
canDrag?: boolean | ((monitor: DragSourceMonitor<DragObject, DropResult>) => boolean);
/** Custom logic for determining drag state */
isDragging?: (monitor: DragSourceMonitor<DragObject, DropResult>) => boolean;
/** Function to collect properties from monitor */
collect?: (monitor: DragSourceMonitor<DragObject, DropResult>) => CollectedProps;
}
type DragObjectFactory<T> = (monitor: DragSourceMonitor<T>) => T | null;
type FactoryOrInstance<T> = T | (() => T);Basic Usage:
import React from "react";
import { useDrag } from "react-dnd";
interface DragItem {
id: string;
name: string;
}
function DraggableCard({ id, name }: DragItem) {
const [{ isDragging }, drag] = useDrag({
type: "card",
item: { id, name },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
return (
<div
ref={drag}
style={{
opacity: isDragging ? 0.5 : 1,
cursor: 'move'
}}
>
{name}
</div>
);
}Advanced Usage with All Options:
import React, { useState } from "react";
import { useDrag } from "react-dnd";
function AdvancedDraggableItem({ id, data, disabled }) {
const [dragCount, setDragCount] = useState(0);
const [collected, drag, preview] = useDrag({
type: "advanced-item",
// Dynamic item factory
item: (monitor) => {
console.log("Drag started");
return { id, data, timestamp: Date.now() };
},
// Conditional dragging
canDrag: !disabled,
// Custom drag state logic
isDragging: (monitor) => {
return monitor.getItem()?.id === id;
},
// Drag end handler
end: (item, monitor) => {
setDragCount(prev => prev + 1);
if (monitor.didDrop()) {
const dropResult = monitor.getDropResult();
console.log("Item dropped:", dropResult);
} else {
console.log("Drag cancelled");
}
},
// Visual options
options: {
dropEffect: "move"
},
// Preview options
previewOptions: {
captureDraggingState: false,
anchorX: 0.5,
anchorY: 0.5
},
// Collect function
collect: (monitor) => ({
isDragging: monitor.isDragging(),
canDrag: monitor.canDrag(),
itemType: monitor.getItemType(),
dragOffset: monitor.getClientOffset(),
}),
});
return (
<div>
<div ref={drag} style={{ opacity: collected.isDragging ? 0.5 : 1 }}>
Draggable Item (dragged {dragCount} times)
</div>
<div ref={preview}>
Custom Preview Content
</div>
</div>
);
}The useDrag hook returns connector functions for attaching drag functionality to DOM elements.
/** Function to connect DOM elements as drag sources */
type ConnectDragSource = DragElementWrapper<DragSourceOptions>;
/** Function to connect custom drag preview elements */
type ConnectDragPreview = DragElementWrapper<DragPreviewOptions>;
type DragElementWrapper<Options> = (
elementOrNode: ConnectableElement,
options?: Options
) => React.ReactElement | null;
type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;Usage Examples:
function CustomConnectorExample() {
const [{ isDragging }, drag, preview] = useDrag({
type: "item",
item: { id: "example" },
collect: (monitor) => ({ isDragging: monitor.isDragging() })
});
return (
<div>
{/* Basic drag connector */}
<button ref={drag}>Drag Handle</button>
{/* Separate preview element */}
<div ref={preview}>
This will be shown during drag
</div>
{/* Connector with options */}
{drag(
<div style={{ padding: 10 }}>
Custom draggable area
</div>,
{ dropEffect: "copy" }
)}
</div>
);
}Monitor interface providing information about the current drag operation.
interface DragSourceMonitor<DragObject = unknown, DropResult = unknown> {
/** Returns true if dragging is allowed */
canDrag(): boolean;
/** Returns true if this component is being dragged */
isDragging(): boolean;
/** Returns the type of item being dragged */
getItemType(): Identifier | null;
/** Returns the dragged item data */
getItem<T = DragObject>(): T;
/** Returns drop result after drop completes */
getDropResult<T = DropResult>(): T | null;
/** Returns true if drop was handled by a target */
didDrop(): boolean;
/** Returns initial pointer coordinates when drag started */
getInitialClientOffset(): XYCoord | null;
/** Returns initial drag source coordinates */
getInitialSourceClientOffset(): XYCoord | null;
/** Returns current pointer coordinates */
getClientOffset(): XYCoord | null;
/** Returns pointer movement since drag start */
getDifferenceFromInitialOffset(): XYCoord | null;
/** Returns projected source coordinates */
getSourceClientOffset(): XYCoord | null;
/** Returns IDs of potential drop targets */
getTargetIds(): Identifier[];
}interface DragSourceOptions {
/** Visual drop effect hint ('move', 'copy', etc.) */
dropEffect?: string;
}interface DragPreviewOptions {
/** Whether to immediately capture dragging state */
captureDraggingState?: boolean;
/** Horizontal anchor point (0-1) */
anchorX?: number;
/** Vertical anchor point (0-1) */
anchorY?: number;
/** Horizontal offset from cursor */
offsetX?: number;
/** Vertical offset from cursor */
offsetY?: number;
}function ConditionalDrag({ item, canEdit }) {
const [{ isDragging }, drag] = useDrag({
type: "item",
item,
canDrag: canEdit && item.status !== "locked",
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
return (
<div
ref={canEdit ? drag : null}
style={{
opacity: isDragging ? 0.5 : 1,
cursor: canEdit ? 'move' : 'default'
}}
>
{item.name}
</div>
);
}function MultiTypeDragItem({ item, mode }) {
const [{ isDragging }, drag] = useDrag({
type: mode === "copy" ? "copyable-item" : "movable-item",
item: { ...item, mode },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
return <div ref={drag}>{item.name}</div>;
}Install with Tessl CLI
npx tessl i tessl/npm-react-dnd