Design Converter
Education
Last updated on Feb 28, 2025
•15 mins read
Last updated on Feb 28, 2025
•15 mins read
Want to add drag-and-drop features to your React app?
Users love interactive elements that make tasks easier. The react ondrag event helps you build smooth dragging experiences.
This blog covers everything from the basics to advanced techniques. You’ll learn how to handle drag events, improve user interactions, and create a better interface.
The ondrag event is triggered when a user drags an element across the screen. Unlike the ondragstart event, which fires only once when dragging begins, the ondrag event is fired continuously throughout the dragging process. This makes it particularly useful for applications where you need to track the dragging state in real-time.
In React applications, the ondrag event is implemented as a JSX attribute that accepts a function. This event handler function receives an event object containing valuable information about the drag operation, including coordinates and the dragged element.
The continuous nature of the on-drag event allows developers to implement features such as real-time previews, dynamic highlighting of valid drop zones, and precise positioning of elements during dragging operations.
Before diving into drag-and-drop functionality, you need to set up your React environment. Start by creating a new React component and importing the necessary modules:
1import React, { useState, useEffect } from 'react'; 2 3const DragDropComponent = () => { 4 // Component logic will go here 5 6 return ( 7 <div className="drag-drop-container"> 8 {/* Component UI */} 9 </div> 10 ); 11}; 12 13export default DragDropComponent;
The React component structure provides the foundation for implementing drag-and-drop operations. By using the default export, you make your component available for import in other parts of your application. This basic setup creates the canvas where you'll implement your drag-and-drop functionality.
The event object passed to your event handler contains crucial information about the drag operation. When a dragging event is fired, this object provides access to properties like:
1const handleDrag = (event) => { 2 console.log('Dragging at coordinates:', event.clientX, event.clientY); 3 console.log('Target element:', event.target); 4 5 // You can access other properties like: 6 // event.target (the dragged element) 7 // event.dataTransfer (contains the data being dragged) 8};
Understanding the event object is essential for creating responsive drag interactions. The event target identifies which HTML element is dragged, while coordinates help track the dragging position. This information can update the component's state and create dynamic visual feedback during the drag operation.
To make an element draggable, you need to set the draggable attribute to true and implement the necessary event handlers:
1import React, { useState } from 'react'; 2 3const DraggableItem = () => { 4 const [isDragging, setIsDragging] = useState(false); 5 6 const handleDragStart = (event) => { 7 // Set the data being dragged 8 event.dataTransfer.setData('text/plain', 'draggable-item-1'); 9 setIsDragging(true); 10 }; 11 12 const handleDrag = (event) => { 13 // React ondrag event handling 14 console.log('Currently dragging'); 15 }; 16 17 const handleDragEnd = () => { 18 setIsDragging(false); 19 }; 20 21 return ( 22 <div 23 draggable={true} 24 onDragStart={handleDragStart} 25 onDrag={handleDrag} 26 onDragEnd={handleDragEnd} 27 style={{ 28 padding: '10px', 29 background: isDragging ? '#f0f0f0' : 'white', 30 border: '1px solid #ccc', 31 cursor: 'move' 32 }} 33 > 34 Drag Me 35 </div> 36 ); 37}; 38 39export default DraggableItem;
The draggable attribute makes the HTML element draggable in supported browsers. The ondragstart event handler sets up the data that will be transferred during the drag operation, while the ondrag event provides continuous feedback as the element is being dragged.
A drop zone is the target area where dragged elements can be released. Creating an effective drop zone involves handling several events:
1import React, { useState } from 'react'; 2 3const DropZone = () => { 4 const [isOver, setIsOver] = useState(false); 5 const [droppedData, setDroppedData] = useState(null); 6 7 const handleDragOver = (event) => { 8 // Prevent default to allow drop 9 event.preventDefault(); 10 setIsOver(true); 11 }; 12 13 const handleDragLeave = () => { 14 setIsOver(false); 15 }; 16 17 const handleDrop = (event) => { 18 event.preventDefault(); 19 20 // Get the data that was set in the drag operation 21 const data = event.dataTransfer.getData('text/plain'); 22 setDroppedData(data); 23 setIsOver(false); 24 25 console.log('Item dropped:', data); 26 }; 27 28 return ( 29 <div 30 onDragOver={handleDragOver} 31 onDragLeave={handleDragLeave} 32 onDrop={handleDrop} 33 style={{ 34 width: '200px', 35 height: '200px', 36 border: '2px dashed #ccc', 37 background: isOver ? '#e0f7fa' : '#f5f5f5', 38 textAlign: 'center', 39 padding: '20px', 40 marginTop: '20px' 41 }} 42 > 43 {droppedData ? `Dropped: ${droppedData}` : 'Drop Here'} 44 </div> 45 ); 46}; 47 48export default DropZone;
The drop zone handles three key events: ondragover, ondragleave, and ondrop. The ondragover event must prevent the default browser behavior to indicate that the area is a valid drop target. When the drop event is fired, the event handler retrieves the data from the dragged element and processes it accordingly.
For complex drag and drop operations, using the useReducer hook can help manage state more effectively:
1import React, { useReducer } from 'react'; 2 3const initialState = { 4 items: ['Item 1', 'Item 2', 'Item 3'], 5 droppedItems: [], 6}; 7 8function reducer(state, action) { 9 switch (action.type) { 10 case 'ITEM_DRAGGED': 11 return { ...state, draggedItem: action.payload }; 12 case 'ITEM_DROPPED': 13 return { 14 ...state, 15 items: state.items.filter(item => item !== state.draggedItem), 16 droppedItems: [...state.droppedItems, state.draggedItem], 17 draggedItem: null 18 }; 19 default: 20 return state; 21 } 22} 23 24const DragDropManager = () => { 25 const [state, dispatch] = useReducer(reducer, initialState); 26 27 const handleDragStart = (event, item) => { 28 event.dataTransfer.setData('text/plain', item); 29 dispatch({ type: 'ITEM_DRAGGED', payload: item }); 30 }; 31 32 const handleDrop = (event) => { 33 event.preventDefault(); 34 dispatch({ type: 'ITEM_DROPPED' }); 35 }; 36 37 return ( 38 <div className="drag-drop-manager"> 39 <div className="draggable-items"> 40 {state.items.map((item, index) => ( 41 <div 42 key={index} 43 draggable 44 onDragStart={(e) => handleDragStart(e, item)} 45 className="draggable-item" 46 > 47 {item} 48 </div> 49 ))} 50 </div> 51 52 <div 53 className="drop-zone" 54 onDragOver={(e) => e.preventDefault()} 55 onDrop={handleDrop} 56 > 57 <h3>Drop Target</h3> 58 {state.droppedItems.map((item, index) => ( 59 <div key={index} className="dropped-item"> 60 {item} 61 </div> 62 ))} 63 </div> 64 </div> 65 ); 66}; 67 68export default DragDropManager;
The useReducer hook provides a structured way to handle different stages of the drag and drop process. By dispatching actions for dragging and dropping, you maintain a clean separation between the events and the state management logic.
Creating a polished drag and drop interface requires attention to user feedback and handling edge cases:
1import React, { useState } from 'react'; 2 3const EnhancedDragDrop = () => { 4 const [draggingItem, setDraggingItem] = useState(null); 5 const [dropTargets, setDropTargets] = useState([ 6 { id: 'target-1', accepts: ['item-1', 'item-3'], items: [] }, 7 { id: 'target-2', accepts: ['item-2'], items: [] } 8 ]); 9 10 const handleDragStart = (event, item) => { 11 event.dataTransfer.setData('application/json', JSON.stringify(item)); 12 13 // Set a custom drag image 14 const dragImage = new Image(); 15 dragImage.src = 'drag-indicator.png'; 16 event.dataTransfer.setDragImage(dragImage, 15, 15); 17 18 setDraggingItem(item); 19 }; 20 21 const isValidDropTarget = (target, item) => { 22 return target.accepts.includes(item.type); 23 }; 24 25 const handleDragOver = (event, target) => { 26 if (draggingItem && isValidDropTarget(target, draggingItem)) { 27 event.preventDefault(); 28 event.dataTransfer.dropEffect = 'move'; 29 } 30 }; 31 32 const handleDrop = (event, targetId) => { 33 event.preventDefault(); 34 35 if (!draggingItem) return; 36 37 setDropTargets(previous => 38 previous.map(target => { 39 if (target.id === targetId && isValidDropTarget(target, draggingItem)) { 40 return { 41 ...target, 42 items: [...target.items, draggingItem] 43 }; 44 } 45 return target; 46 }) 47 ); 48 49 setDraggingItem(null); 50 }; 51 52 return ( 53 <div className="enhanced-drag-drop"> 54 <div className="draggable-items"> 55 <div 56 draggable 57 onDragStart={(e) => handleDragStart(e, { id: 1, type: 'item-1', text: 'Item 1' })} 58 className="item" 59 > 60 Item 1 61 </div> 62 <div 63 draggable 64 onDragStart={(e) => handleDragStart(e, { id: 2, type: 'item-2', text: 'Item 2' })} 65 className="item" 66 > 67 Item 2 68 </div> 69 </div> 70 71 <div className="drop-targets"> 72 {dropTargets.map(target => ( 73 <div 74 key={target.id} 75 className={`drop-target ${draggingItem && isValidDropTarget(target, draggingItem) ? 'valid-target' : ''}`} 76 onDragOver={(e) => handleDragOver(e, target)} 77 onDrop={(e) => handleDrop(e, target.id)} 78 > 79 <h4>{target.id}</h4> 80 {target.items.map((item, i) => ( 81 <div key={i} className="dropped-item"> 82 {item.text} 83 </div> 84 ))} 85 </div> 86 ))} 87 </div> 88 </div> 89 ); 90}; 91 92export default EnhancedDragDrop;
This implementation introduces several enhancements: custom drag images, visual feedback for valid drop targets, and restrictions on which drop zones accept specific items. By checking if a drop target is valid before allowing the drop operation, you create a more intuitive user experience.
When implementing drag and drop functionality, several common issues might arise:
1const handleDragOver = (event) => { 2 // This is crucial for the drop event to fire 3 event.preventDefault(); 4};
1// Safe approach for complex data 2event.dataTransfer.setData('text/plain', JSON.stringify(myComplexObject)); 3 4// Retrieving the data 5const data = JSON.parse(event.dataTransfer.getData('text/plain'));
The drop API is generally well-supported in modern browsers, but edge cases in older browsers might require specific handling or alternative approaches.
Once you've mastered the basics, you can implement more advanced drag and drop features:
1import React, { useState, useRef } from 'react'; 2 3const AdvancedDragDrop = () => { 4 const [items, setItems] = useState([ 5 { id: 1, text: 'Item 1', category: 'A' }, 6 { id: 2, text: 'Item 2', category: 'B' }, 7 { id: 3, text: 'Item 3', category: 'A' } 8 ]); 9 10 const [categories, setCategories] = useState([ 11 { id: 'A', name: 'Category A', items: [] }, 12 { id: 'B', name: 'Category B', items: [] } 13 ]); 14 15 const draggedItemRef = useRef(null); 16 17 const handleDragStart = (event, item) => { 18 draggedItemRef.current = item; 19 20 // Create a ghost image that follows the cursor 21 const ghost = event.target.cloneNode(true); 22 ghost.style.position = 'absolute'; 23 ghost.style.opacity = '0.5'; 24 ghost.style.pointerEvents = 'none'; 25 document.body.appendChild(ghost); 26 27 event.dataTransfer.setDragImage(ghost, 0, 0); 28 setTimeout(() => { 29 ghost.remove(); 30 }, 0); 31 32 event.dataTransfer.effectAllowed = 'move'; 33 }; 34 35 const handleDrop = (event, categoryId) => { 36 event.preventDefault(); 37 38 const draggedItem = draggedItemRef.current; 39 if (!draggedItem) return; 40 41 // Remove item from original list 42 setItems(prev => prev.filter(item => item.id !== draggedItem.id)); 43 44 // Add to category 45 setCategories(prev => 46 prev.map(category => { 47 if (category.id === categoryId) { 48 return { 49 ...category, 50 items: [...category.items, draggedItem] 51 }; 52 } 53 return category; 54 }) 55 ); 56 57 draggedItemRef.current = null; 58 }; 59 60 // Handle dragging multiple items 61 const [selectedItems, setSelectedItems] = useState([]); 62 63 const toggleItemSelection = (item) => { 64 if (selectedItems.find(i => i.id === item.id)) { 65 setSelectedItems(prev => prev.filter(i => i.id !== item.id)); 66 } else { 67 setSelectedItems(prev => [...prev, item]); 68 } 69 }; 70 71 const handleMultiDragStart = (event) => { 72 if (selectedItems.length > 0) { 73 event.dataTransfer.setData('application/json', JSON.stringify(selectedItems)); 74 draggedItemRef.current = selectedItems; 75 } 76 }; 77 78 return ( 79 <div className="advanced-drag-drop"> 80 <div className="items-container"> 81 {items.map(item => ( 82 <div 83 key={item.id} 84 draggable 85 onClick={() => toggleItemSelection(item)} 86 onDragStart={(e) => handleDragStart(e, item)} 87 className={`draggable-item ${selectedItems.includes(item) ? 'selected' : ''}`} 88 > 89 {item.text} 90 </div> 91 ))} 92 93 {selectedItems.length > 0 && ( 94 <button 95 draggable 96 onDragStart={handleMultiDragStart} 97 > 98 Drag {selectedItems.length} items 99 </button> 100 )} 101 </div> 102 103 <div className="categories"> 104 {categories.map(category => ( 105 <div 106 key={category.id} 107 className="category-drop-zone" 108 onDragOver={(e) => e.preventDefault()} 109 onDrop={(e) => handleDrop(e, category.id)} 110 > 111 <h3>{category.name}</h3> 112 {category.items.map(item => ( 113 <div key={item.id} className="category-item"> 114 {item.text} 115 </div> 116 ))} 117 </div> 118 ))} 119 </div> 120 </div> 121 ); 122}; 123 124export default AdvancedDragDrop;
This advanced implementation introduces several sophisticated features:
Custom ghost images that follow the cursor during dragging
Multiple item selection and dragging
Categorization of dragged elements
Advanced visual feedback during the drag operation
These techniques create a more intuitive and powerful drag-and-drop experience, suitable for complex applications such as Kanban boards, file managers, or image galleries.
React's drag and drop can also handle file uploads, allowing users to drag files from their computer onto your application:
1import React, { useState } from 'react'; 2 3const FileDrop = () => { 4 const [files, setFiles] = useState([]); 5 const [isDragging, setIsDragging] = useState(false); 6 7 const handleDragEnter = (event) => { 8 event.preventDefault(); 9 setIsDragging(true); 10 }; 11 12 const handleDragLeave = () => { 13 setIsDragging(false); 14 }; 15 16 const handleDragOver = (event) => { 17 event.preventDefault(); 18 }; 19 20 const handleDrop = (event) => { 21 event.preventDefault(); 22 setIsDragging(false); 23 24 const droppedFiles = Array.from(event.dataTransfer.files); 25 setFiles(prev => [...prev, ...droppedFiles]); 26 27 console.log('Files dropped:', droppedFiles); 28 }; 29 30 return ( 31 <div 32 className={`file-drop-zone ${isDragging ? 'dragging' : ''}`} 33 onDragEnter={handleDragEnter} 34 onDragLeave={handleDragLeave} 35 onDragOver={handleDragOver} 36 onDrop={handleDrop} 37 style={{ 38 border: `2px dashed ${isDragging ? '#2196f3' : '#ccc'}`, 39 padding: '20px', 40 textAlign: 'center', 41 minHeight: '200px' 42 }} 43 > 44 <p>Drag and drop files here</p> 45 46 {files.length > 0 && ( 47 <div className="dropped-files"> 48 <h4>Dropped Files:</h4> 49 <ul> 50 {files.map((file, index) => ( 51 <li key={index}>{file.name} ({Math.round(file.size / 1024)} KB)</li> 52 ))} 53 </ul> 54 </div> 55 )} 56 </div> 57 ); 58}; 59 60export default FileDrop;
This component creates a drop zone for files, handling the drop events to collect and display the dropped files. The browser's dataTransfer object provides access to the files property, which contains an array of the dropped files.
Another common use case for drag and drop is creating sortable lists:
1import React, { useState } from 'react'; 2 3const SortableList = () => { 4 const [items, setItems] = useState([ 5 { id: 1, text: 'Item 1' }, 6 { id: 2, text: 'Item 2' }, 7 { id: 3, text: 'Item 3' }, 8 { id: 4, text: 'Item 4' } 9 ]); 10 11 const [draggedIndex, setDraggedIndex] = useState(null); 12 13 const handleDragStart = (event, index) => { 14 setDraggedIndex(index); 15 event.dataTransfer.effectAllowed = 'move'; 16 }; 17 18 const handleDragOver = (event, index) => { 19 event.preventDefault(); 20 21 if (draggedIndex === null) return; 22 23 // Don't do anything if dragging over the same item 24 if (draggedIndex === index) return; 25 26 // Reorder the items 27 const newItems = [...items]; 28 const draggedItem = newItems[draggedIndex]; 29 30 // Remove the dragged item 31 newItems.splice(draggedIndex, 1); 32 33 // Insert at the new position 34 newItems.splice(index, 0, draggedItem); 35 36 setItems(newItems); 37 setDraggedIndex(index); 38 }; 39 40 const handleDragEnd = () => { 41 setDraggedIndex(null); 42 }; 43 44 return ( 45 <div className="sortable-list"> 46 <h3>Drag to Reorder</h3> 47 <ul> 48 {items.map((item, index) => ( 49 <li 50 key={item.id} 51 draggable 52 onDragStart={(e) => handleDragStart(e, index)} 53 onDragOver={(e) => handleDragOver(e, index)} 54 onDragEnd={handleDragEnd} 55 style={{ 56 padding: '10px', 57 margin: '5px 0', 58 background: draggedIndex === index ? '#f0f0f0' : 'white', 59 border: '1px solid #ddd', 60 cursor: 'move' 61 }} 62 > 63 {item.text} 64 </li> 65 ))} 66 </ul> 67 </div> 68 ); 69}; 70 71export default SortableList;
This sortable list implementation uses the drag events to reorder items in real-time as they're being dragged. The drag target is determined by the current mouse position, and the list updates dynamically to show the new order.
When implementing drag and drop in large applications, performance can become a concern:
1import React, { useState, useCallback, memo } from 'react'; 2 3// Memoized draggable item component 4const DraggableItem = memo(({ item, onDragStart }) => { 5 return ( 6 <div 7 draggable 8 onDragStart={(e) => onDragStart(e, item)} 9 className="draggable-item" 10 > 11 {item.text} 12 </div> 13 ); 14}); 15 16// Memoized drop zone component 17const DropZone = memo(({ onDrop, onDragOver, children }) => { 18 return ( 19 <div 20 className="drop-zone" 21 onDragOver={onDragOver} 22 onDrop={onDrop} 23 > 24 {children} 25 </div> 26 ); 27}); 28 29const PerformantDragDrop = () => { 30 const [items, setItems] = useState( 31 Array.from({ length: 100 }, (_, i) => ({ 32 id: i, 33 text: `Item ${i}`, 34 category: i % 3 === 0 ? 'A' : 'B' 35 })) 36 ); 37 38 const [categories, setCategories] = useState({ 39 A: { name: 'Category A', items: [] }, 40 B: { name: 'Category B', items: [] } 41 }); 42 43 // Memoized event handlers 44 const handleDragStart = useCallback((event, item) => { 45 event.dataTransfer.setData('application/json', JSON.stringify(item)); 46 }, []); 47 48 const handleDragOver = useCallback((event) => { 49 event.preventDefault(); 50 }, []); 51 52 const handleDrop = useCallback((event, category) => { 53 event.preventDefault(); 54 55 try { 56 const item = JSON.parse(event.dataTransfer.getData('application/json')); 57 58 setItems(prev => prev.filter(i => i.id !== item.id)); 59 setCategories(prev => ({ 60 ...prev, 61 [category]: { 62 ...prev[category], 63 items: [...prev[category].items, item] 64 } 65 })); 66 } catch (error) { 67 console.error('Error processing drop:', error); 68 } 69 }, []); 70 71 return ( 72 <div className="performant-drag-drop"> 73 <div className="items-container"> 74 {items.map(item => ( 75 <DraggableItem 76 key={item.id} 77 item={item} 78 onDragStart={handleDragStart} 79 /> 80 ))} 81 </div> 82 83 <div className="categories-container"> 84 {Object.entries(categories).map(([key, category]) => ( 85 <DropZone 86 key={key} 87 onDragOver={handleDragOver} 88 onDrop={(e) => handleDrop(e, key)} 89 > 90 <h3>{category.name}</h3> 91 <div className="category-items"> 92 {category.items.map(item => ( 93 <div key={item.id} className="category-item"> 94 {item.text} 95 </div> 96 ))} 97 </div> 98 </DropZone> 99 ))} 100 </div> 101 </div> 102 ); 103}; 104 105export default PerformantDragDrop;
This implementation uses several performance optimization techniques:
Memoized components with React.memo to prevent unnecessary re-renders
useCallback for event handlers to maintain stable references
Component composition to isolate re-renders to specific parts of the UI
Efficient state updates that minimize rendering cycles
These optimizations help maintain smooth performance even with large lists of draggable elements.
Ensuring consistent drag and drop behavior across different browsers requires attention to browser-specific issues:
1import React, { useState, useEffect } from 'react'; 2 3const CrossBrowserDragDrop = () => { 4 const [isEdge, setIsEdge] = useState(false); 5 const [isFirefox, setIsFirefox] = useState(false); 6 7 useEffect(() => { 8 // Detect browser 9 const userAgent = navigator.userAgent; 10 setIsEdge(userAgent.indexOf("Edge") > -1); 11 setIsFirefox(userAgent.indexOf("Firefox") > -1); 12 }, []); 13 14 const handleDragStart = (event, data) => { 15 // Set text data for all browsers 16 event.dataTransfer.setData('text/plain', JSON.stringify(data)); 17 18 // For Firefox, we need to set effectAllowed 19 if (isFirefox) { 20 event.dataTransfer.effectAllowed = 'move'; 21 } 22 23 // For Edge, which doesn't support custom drag images well 24 if (!isEdge) { 25 const img = new Image(); 26 img.src = 'drag-image.png'; 27 event.dataTransfer.setDragImage(img, 10, 10); 28 } 29 }; 30 31 const handleDragOver = (event) => { 32 event.preventDefault(); 33 34 // Set dropEffect consistently across browsers 35 event.dataTransfer.dropEffect = 'move'; 36 }; 37 38 const handleDrop = (event) => { 39 event.preventDefault(); 40 41 try { 42 // Get the data (consistent across browsers) 43 const data = JSON.parse(event.dataTransfer.getData('text/plain')); 44 console.log('Dropped data:', data); 45 } catch (e) { 46 console.error('Error parsing dropped data:', e); 47 } 48 }; 49 50 return ( 51 <div className="cross-browser-drag-drop"> 52 <div 53 draggable 54 onDragStart={(e) => handleDragStart(e, { id: 1, text: 'Example Item' })} 55 className="draggable-item" 56 > 57 Drag Me ({isEdge ? 'Edge' : isFirefox ? 'Firefox' : 'Chrome/Safari'}) 58 </div> 59 60 <div 61 className="drop-target" 62 onDragOver={handleDragOver} 63 onDrop={handleDrop} 64 > 65 Drop Target 66 </div> 67 </div> 68 ); 69}; 70 71export default CrossBrowserDragDrop;
This implementation detects the browser and applies specific adjustments for Edge and Firefox, which have some differences in their drag and drop implementations. By using a consistent data format ('text/plain') and ensuring proper event handling, you can create a drag and drop experience that works reliably across all supported browsers.
The React onDrag event helps create smooth and interactive user experiences. Whether you're building simple draggable elements or complex file uploads, mastering this feature can make your applications more user-friendly.
Pay attention to details like visual feedback, performance, and cross-browser support. Small improvements can make a big difference in how users interact with your app.
Keep refining your drag-and-drop features to make them intuitive and easy to use. A well-designed interface keeps users engaged and makes navigation feel natural.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.