Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
HTML5 Drag and Drop File Upload React Component
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const ANIMATION_DURATION = 1000;
class BatchDropZone extends React.Component {
static propTypes = {
// function that recieves an array of files
receiveFiles: PropTypes.func.isRequired,
// will be passed as second argument of receiveFiles function if present
id: PropTypes.string,
// if single is set then file input will NOT be multiple
single: PropTypes.bool,
// text message that will appear in drop zone
copy: PropTypes.string,
// additional classes that will be added to the drop zone wrapper
className: PropTypes.string,
}
state = {}
handleDragOver = e => {
e.preventDefault();
if (this.state.inDropZone) {
return;
}
this.setState({ inDropZone: true });
}
handleDrop = e => {
e.preventDefault();
const { dataTransfer } = e;
const files = [];
if (dataTransfer.items) {
for (let i = this.props.single ? dataTransfer.items.length - 1 : 0; i < dataTransfer.items.length; i++) {
if (dataTransfer.items[i].kind == "file") {
const file = dataTransfer.items[i].getAsFile();
files.push(file);
}
}
} else {
for (let i = this.props.single ? dataTransfer.files.length - 1 : 0; i < dataTransfer.files.length; i++) {
files.push(dataTransfer.files[i]);
}
}
this.setState({
uploading: true,
});
setTimeout(() => {
this.setState({
uploading: false,
inDropZone: false,
});
this.props.receiveFiles(files, this.props.id);
}, ANIMATION_DURATION);
}
handleDragLeave = () => {
if (!this.state.inDropZone) return;
this.setState({ inDropZone: false });
}
handleClick = () => this.fileInput && this.fileInput.click();
handleFilesFromInput = (e) => {
const files = [];
Array.from(e.currentTarget.files).forEach(file => {
files.push(file);
});
this.setState({
uploading: true,
});
setTimeout(() => {
this.setState({
uploading: false,
inDropZone: false,
});
this.props.receiveFiles(files, this.props.id);
}, ANIMATION_DURATION);
}
render() {
const classes = classNames(`drop-it-wrap batch ${this.props.className || ''}`, {
active: this.state.inDropZone,
uploading: this.state.uploading,
});
const dropEvents = {
onDrop: this.handleDrop,
onDragOver: this.handleDragOver,
onDragLeave: this.handleDragLeave,
onClick: this.handleClick,
};
let err;
if (this.state.error) {
err = (
<div className="notification danger">
{this.state.error}
</div>
);
}
const fileInputAttrs = {
ref: (c) => { this.fileInput = c; },
type: 'file',
onChange: this.handleFilesFromInput,
style: { position: 'absolute', left: -99999999 },
};
if (!this.props.single) {
fileInputAttrs.multiple = true;
}
return (
<div style={{ width: '100%', height: '100%' }}>
{err}
<div className={classes} {...dropEvents}>
<div className="loader">
Processing ...
</div>
<input {...fileInputAttrs} />
<div className="default">
{this.props.copy || 'Drop image files here or click'}
</div>
</div>
</div>
);
}
}
export default BatchDropZone;
import DropZone from './DropZone';
export const readAsBase64 = (file) => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.addEventListener('load', () => {
resolve({file, dataURL: reader.result});
});
reader.readAsDataURL(file);
});
}
export default DropZone;
$primary: blue;
$secondary: red;
.drop-it-wrap{
width: 100%;
border: 5px dashed #ececec;
height: 250px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.loader{
display: none;
}
&.uploading{
.loader{
display: block;
}
.default{
display: none;
}
}
&.active{
border-color: $primary;
}
&:hover{
border: 5px dashed darken(#ececec, 10%);
}
}
.drop-it-input{
position: absolute;
top: -99999999px;
}
@keyframes disco {
0% {
background: $primary;
}
100% {
background: $secondary;
}
}
.batch {
cursor: crosshair;
&.uploading {
animation: disco .1s infinite alternate;
}
background: #ffffff;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment