Drag and drop functionality can greatly enhance the user experience in web applications. In this guide, we'll explore how to implement drag and drop in React using both native HTML5 drag and drop API and popular libraries like react-beautiful-dnd.
Drag and drop functionality allows users to intuitively interact with elements on a page by clicking and dragging them to new locations. This can be particularly useful for tasks such as reordering lists, moving items between containers, or uploading files.
In React, we can implement drag and drop in two main ways:
Let's explore both approaches in detail.
The HTML5 drag and drop API provides a set of events and methods that we can use to implement basic drag and drop functionality. Here's a step-by-step guide to implementing it in React:
To make an element draggable, we need to add the draggable
attribute and handle the onDragStart
event:
function DraggableItem({ id, content }) {
const handleDragStart = (e) => {
e.dataTransfer.setData('text/plain', id);
e.dataTransfer.effectAllowed = 'move';
};
return (
<div draggable onDragStart={handleDragStart}>
{content}
</div>
);
}
Next, we need to create areas where items can be dropped. We'll handle the onDragOver
and onDrop
events:
function DropZone({ onDrop }) {
const handleDragOver = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e) => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
onDrop(id);
};
return (
<div
onDragOver={handleDragOver}
onDrop={handleDrop}
style={{ minHeight: '100px', border: '1px dashed #ccc' }}
>
Drop items here
</div>
);
}
Now we can use these components to create a simple drag and drop interface:
function DragDropExample() {
const [items, setItems] = useState([
{ id: '1', content: 'Item 1' },
{ id: '2', content: 'Item 2' },
{ id: '3', content: 'Item 3' },
]);
const [droppedItems, setDroppedItems] = useState([]);
const handleDrop = (id) => {
const item = items.find((item) => item.id === id);
setItems(items.filter((item) => item.id !== id));
setDroppedItems([...droppedItems, item]);
};
return (
<div>
<h2>Draggable Items</h2>
{items.map((item) => (
<DraggableItem key={item.id} id={item.id} content={item.content} />
))}
<h2>Drop Zone</h2>
<DropZone onDrop={handleDrop} />
<h2>Dropped Items</h2>
{droppedItems.map((item) => (
<div key={item.id}>{item.content}</div>
))}
</div>
);
}
This example demonstrates a basic implementation of drag and drop using the native HTML5 API. However, it has limitations in terms of animations, touch support, and complex drag behaviors.
First, let's install react-dropzone:
npm install react-dropzone
Let's start with a basic implementation of react-dropzone:
import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
function BasicDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
console.log(acceptedFiles);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
);
}
export default BasicDropzone;
This creates a simple dropzone that logs the accepted files to the console.
Let's enhance our dropzone by adding file previews:
import React, { useCallback, useState, useEffect } from 'react';
import { useDropzone } from 'react-dropzone';
function DropzoneWithPreviews() {
const [files, setFiles] = useState([]);
const onDrop = useCallback(acceptedFiles => {
setFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}, []);
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop
});
const thumbs = files.map(file => (
<div key={file.name}>
<img
src={file.preview}
alt={file.name}
style={{ width: '100px', height: '100px' }}
/>
</div>
));
useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks
return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, [files]);
return (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside>
{thumbs}
</aside>
</section>
);
}
export default DropzoneWithPreviews;
React-dropzone can easily handle multiple files:
import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
function MultipleFileDropzone() {
const onDrop = useCallback((acceptedFiles) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
reader.onabort = () => console.log('file reading was aborted');
reader.onerror = () => console.log('file reading has failed');
reader.onload = () => {
// Do whatever you want with the file contents
const binaryStr = reader.result;
console.log(binaryStr);
};
reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
);
}
export default MultipleFileDropzone;
You can easily style your dropzone. Here's an example with some custom styling:
import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
function StyledDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Handle file drop
}, []);
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject
} = useDropzone({ onDrop, accept: { 'image/*': [] } });
const style = {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '20px',
borderWidth: 2,
borderRadius: 2,
borderColor: '#eeeeee',
borderStyle: 'dashed',
backgroundColor: '#fafafa',
color: '#bdbdbd',
outline: 'none',
transition: 'border .24s ease-in-out'
};
const activeStyle = {
borderColor: '#2196f3'
};
const acceptStyle = {
borderColor: '#00e676'
};
const rejectStyle = {
borderColor: '#ff1744'
};
return (
<div {...getRootProps({
style: {
...style,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}
})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
);
}
export default StyledDropzone;
When working with large files or many files, consider the following:
useCallback
for your onDrop
function to prevent unnecessary re-renders.Example of file size and type restrictions:
const { getRootProps, getInputProps } = useDropzone({
onDrop,
accept: {
'image/jpeg': [],
'image/png': []
},
maxSize: 5242880, // 5MB
multiple: false
});
To make your dropzone accessible:
Example:
<div
{...getRootProps()}
role="button"
aria-label="Drag and drop area. Click to select files"
>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
2024 © All rights reserved - buraxta.com