React File Upload Drag and Drop

React File Upload Drag and Drop
Project: React.js drag&drop file upload
Author: Maz
Edit Online: View on CodePen
License: MIT

This React JS component helps you to create drag and drop file upload functionality on your webpage. It uses FileReader API to handle multiple files with preview and remove features. The module exports a FileUpload component that allows users to drag and drop files, select files through a file picker, preview selected files, and delete files from the upload queue.

How to Create File Uploader with Drag and Drop Feature in React JS

First of all, create a div element with a class name “card” and define its unique id in which React JS will render the file uploader.

<div class="card" id="app"></div>

After that, add the following CSS styles to your project for the basic UI of the file uploader. You can modify the CSS rules according to your needs.

body {
  margin: 0;
  font-family: sans-serif;
  display: flex;
  justify-content: center;
  background: #90A4AE;
  color: #212121;
  min-width: 320px;
  padding-top: 100px;
}

*, *::before, *::after {
  box-sizing: border-box;
}

.card {
  min-width: 288px;
  margin: auto;
  width: 500px;
  font-weight: lighter;
  max-width: calc(100% - 32px);
  padding: 1.5em 1em;
  position: relative;
  background: #FFFFFF;
  color: #212121;
  border-radius: 2px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35), 0 3px 6px rgba(0, 0, 0, 0.65);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

.file-drag {
  margin: 1em 0;
  cursor: default;
  background-color: #ECEFF1;
  color: #607D8B;
  background-image: linear-gradient(to top, currentColor 10px, #FFFFFF 10px), linear-gradient(to right, currentColor 10px, #FFFFFF 10px), linear-gradient(to bottom, currentColor 10px, #FFFFFF 10px), linear-gradient(to left, currentColor 10px, #FFFFFF 10px);
  background-size: 1px 20px, 20px 1px, 1px 20px, 20px 1px;
  background-repeat: repeat-y, repeat-x, repeat-y, repeat-x;
  -webkit-animation: background-position 1s linear infinite;
          animation: background-position 1s linear infinite;
  will-change: background-position;
}
.file-drag.hover {
  background-color: #E8F5E9;
  color: #4CAF50;
}
@-webkit-keyframes background-position {
  from {
    background-position: 0% 0px, 0px 100%, 100% 0px, 0px 0%;
  }
  to {
    background-position: 0% 40px, 40px 100%, 100% -40px, -40px 0%;
  }
}
@keyframes background-position {
  from {
    background-position: 0% 0px, 0px 100%, 100% 0px, 0px 0%;
  }
  to {
    background-position: 0% 40px, 40px 100%, 100% -40px, -40px 0%;
  }
}

.image-preview {
  display: inline-block;
  max-width: 60px;
  max-height: 60px;
  margin: 2px;
  padding: 5px;
}

.preview {
  display: inline-block;
  padding: 5px;
  margin: 2px;
  max-width: 200px;
  max-height: 100px;
  overflow: auto;
  word-break: break-all;
  white-space: pre-wrap;
}

.preview-item {
  display: flex;
  align-items: center;
  align-content: center;
  margin: 5px 0;
  background: #CFD8DC;
}
.preview-item.disabled {
  background: #F5F5F5;
  pointer-events: none;
}
.preview-item.disabled .button {
  opacity: 0.5;
}

.input {
  position: absolute;
  top: 0;
  left: -10em;
  height: 0;
  width: 0;
  opacity: 0;
}

.loader {
  height: 60px;
  width: 60px;
  position: relative;
}

.loader-item {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 10px;
  width: 10px;
  margin: -5px;
  border-radius: 100%;
  background: #90A4AE;
  -webkit-animation: load-item 0.8s ease-in-out infinite;
          animation: load-item 0.8s ease-in-out infinite;
}
.loader-item:nth-child(1) {
  -webkit-animation-delay: 0s;
          animation-delay: 0s;
}
.loader-item:nth-child(2) {
  -webkit-animation-delay: -0.2s;
          animation-delay: -0.2s;
}
.loader-item:nth-child(3) {
  -webkit-animation-delay: -0.4s;
          animation-delay: -0.4s;
}

@-webkit-keyframes load-item {
  from {
    transform: rotate(0deg) translateY(20px);
  }
  to {
    transform: rotate(360deg) translateY(20px);
  }
}

@keyframes load-item {
  from {
    transform: rotate(0deg) translateY(20px);
  }
  to {
    transform: rotate(360deg) translateY(20px);
  }
}
.button {
  font-size: 0.8em;
  margin-right: 0.5em;
  padding: 0.35em 0.5em;
  color: currentColor;
  border: 1px solid;
  cursor: pointer;
  background: transparent;
  color: #607D8B;
  white-space: nowrap;
  font-weight: lighter;
  text-transform: uppercase;
}
.button:hover, .button:focus {
  background: #607D8B;
  border-color: #607D8B;
  color: #FFFFFF;
}
.button:active {
  background: #708a97;
  border-color: #708a97;
  color: #FFFFFF;
}
.button:focus {
  outline: 0;
}

.input-wrapper {
  position: relative;
  width: 100%;
  overflow: hidden;
  color: #212121;
}

.file-name {
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-ext {
  flex: 1;
}

.input-cover {
  padding: 1em;
  display: flex;
  align-items: center;
  align-content: center;
  border-radius: 2px;
}

.help-text {
  padding: 1em;
  display: inline-block;
  color: #212121;
}

.spacer {
  flex: 1;
}

Load the React JS by adding the following CDN links before closing the body tag:

<script src='https://npmcdn.com/react@15.3.0/dist/react.min.js'></script>
<script src='https://npmcdn.com/react-dom@15.3.0/dist/react-dom.min.js'></script>

Finally, add the following JavaScript function to activate the file uploader.

console.clear();
const {
  createClass,
  PropTypes } =
React;
const {
  render } =
ReactDOM;

const styles = {
  inputWrapper: 'input-wrapper',
  inputCover: 'input-cover',
  helpText: 'help-text',
  fileName: 'file-name',
  fileNameStretch: 'file-name spacer',
  fileExt: 'file-ext',
  fileDrag: 'file-drag',
  input: 'input',
  loader: 'loader',
  disabled: 'disabled',
  loading: 'loading',
  loaderItem: 'loader-item',
  spacer: 'spacer',
  button: 'button',
  hover: 'hover',
  imagePreview: 'image-preview',
  preview: 'preview',
  previewItem: 'preview-item',
  previews: 'previews' };


const uploadFileToServer = file => {
  const delay = file.size / 100;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, delay);
  });
};

const getExtFromType = type => {
  const parts = type.split('/');
  return parts[parts.length - 1];
};
const getExtFromName = name => {
  const parts = name.split('.');
  return parts[parts.length - 1];
};

const Loader = () => {
  return /*#__PURE__*/React.createElement("div", { className: styles.loader }, /*#__PURE__*/
  React.createElement("span", { className: styles.loaderItem }), /*#__PURE__*/
  React.createElement("span", { className: styles.loaderItem }), /*#__PURE__*/
  React.createElement("span", { className: styles.loaderItem }));

};

const FilePreview = createClass({
  getInitialState() {
    return {
      loading: true };

  },
  getDefaultProps() {
    return {
      onRemove: () => {} };

  },
  componentWillMount() {
    this.loadData();
  },
  componentWillReceiveProps(newProps) {
    this.loadData(newProps.data);
  },
  loadData(data = this.props.data) {
    if (!data) {
      return;
    }
    const reader = new FileReader();
    const type =
    data.type.match('text') ?
    'text' :
    data.type.match('image') ?
    'image' :

    data.type;


    reader.onload = e => {
      const src = e.target.result;
      this.setState({
        src,
        type,
        loading: false });

    };
    if (type === 'text') {
      reader.readAsText(data);

    } else if (type === 'image') {
      reader.readAsDataURL(data);
    } else {
      this.setState({
        src: false,
        type,
        loading: false });

    }
  },
  render() {
    const loading =
    this.state.loading ?
    'loading data...' :

    null;


    const uploading =
    this.props.data.loading ? /*#__PURE__*/
    React.createElement(Loader, null) :

    null;


    const preview =
    !this.state.loading && !this.props.data.loading ?
    this.state.type === 'text' ? /*#__PURE__*/
    React.createElement("pre", { className: styles.preview }, this.state.src) :
    this.state.type === 'image' ? /*#__PURE__*/
    React.createElement("img", { alt: "preview", src: this.state.src, className: styles.imagePreview }) : /*#__PURE__*/

    React.createElement("pre", { className: styles.preview }, "no preview") :


    null;


    const classes = [
    styles.previewItem,
    this.props.data.loading ? styles.disabled : ''].
    join(' ').trim();
    return /*#__PURE__*/(
      React.createElement("div", { className: classes },
      uploading,
      loading,
      preview, /*#__PURE__*/
      React.createElement("div", { className: styles.fileNameStretch }, this.props.data.name), /*#__PURE__*/
      React.createElement("button", { className: styles.button,
        onClick: this.props.onRemove }, "remove"), /*#__PURE__*/


      React.createElement("button", { className: styles.button,
        onClick: this.props.onUpload }, "upload")));




  } });


const FileUpload = React.createClass({ displayName: "FileUpload",
  getInitialState() {
    return {
      fileList: [] };

  },

  handleDragOver(e) {
    if ('preventDefault' in e) {
      e.stopPropagation();
      e.preventDefault();
    }
    const hoverState =
    e.type === 'dragover' ?
    styles.hover :

    null;


    this.setState({
      hoverState });

  },
  handleFileSelect(e) {
    this.handleDragOver(e);
    const files = e.target.files || e.dataTransfer.files;
    const fileList = Object.keys(files).map(file => files[file]);
    this.setState({
      fileList });

  },

  removeItem(index) {
    const fileList = this.state.fileList;
    fileList.splice(index, 1);
    this.setState({
      fileList });

  },
  removeFile(file) {
    const fileList = this.state.fileList;
    const index = fileList.indexOf(file);
    this.removeItem(index);
  },
  uploadFile(file) {
    return new Promise((resolve, reject) => {
      const fileList = this.state.fileList;
      const index = fileList.indexOf(file);
      fileList[index].loading = true;
      this.setState({ fileList });
      if (typeof file === 'file' || !('size' in file)) {
        return reject(new Error('No file size'));
      }
      this.props.onUpload(file).then(data => {
        resolve(data);
      });
    });
  },

  previews() {
    return this.state.fileList.map((file, index) => {
      const removeItem = () => {
        this.removeItem(index);
      };
      const uploadFile = () => {
        this.uploadFile(file).then(() => {
          this.removeFile(file);
        });
      };
      return /*#__PURE__*/(
        React.createElement(FilePreview, { key: index,
          data: file,
          onRemove: removeItem,
          onUpload: uploadFile }));

    });
  },
  uploadFiles() {
    this.state.fileList.forEach(file => {
      this.uploadFile(file).then(() => {
        this.removeFile(file);
      });
    });
  },
  selectFile(e) {
    e.preventDefault();
    this.input.click(e);
  },
  render() {
    const {
      maxSize,
      name,
      multiple,
      label } =
    this.props;

    const dragClasses = [
    styles.fileDrag,
    this.state.hoverState].
    join(' ').trim();
    const fileExt =
    this.state.fileList.length === 1 ?
    this.state.fileList[0].type ?
    `.${getExtFromType(this.state.fileList[0].type)}` :

    `.${getExtFromName(this.state.fileList[0].name)}` :


    null;


    const extTail =
    fileExt ? /*#__PURE__*/
    React.createElement("span", { className: styles.fileExt }, fileExt) :

    null;


    const fileNames =
    this.state.fileList.length > 1 ?
    `${this.state.fileList.length} Files` :
    this.state.fileList.length === 1 ?
    this.state.fileList[0].name.replace(fileExt, '') :

    'No file chosen';



    return /*#__PURE__*/(
      React.createElement("div", null, /*#__PURE__*/
      React.createElement("input", { type: "hidden", name: `${name}:maxSize`, value: maxSize }), /*#__PURE__*/
      React.createElement("div", null, /*#__PURE__*/
      React.createElement("label", null, /*#__PURE__*/
      React.createElement("span", null, label), /*#__PURE__*/
      React.createElement("div", { className: dragClasses,
        onDragOver: this.handleDragOver,
        onDragLeave: this.handleDragOver,
        onDrop: this.handleFileSelect }, /*#__PURE__*/
      React.createElement("div", { className: styles.inputWrapper }, /*#__PURE__*/
      React.createElement("input", { type: "file",
        tabIndex: "-1",
        ref: x => this.input = x,
        className: styles.input,
        name: name,
        multiple: multiple,
        onChange: this.handleFileSelect }), /*#__PURE__*/
      React.createElement("div", { className: styles.inputCover }, /*#__PURE__*/
      React.createElement("button", { className: styles.button,
        type: "button",
        onClick: this.selectFile }, "Choose Files"), /*#__PURE__*/

      React.createElement("span", { className: styles.fileName }, fileNames),
      extTail)), /*#__PURE__*/


      React.createElement("span", { className: styles.helpText }, "or drop files here"))), /*#__PURE__*/

      React.createElement("button", { className: styles.button,
        type: "button",
        onClick: this.uploadFiles }, "Upload All"), /*#__PURE__*/


      React.createElement("div", { className: styles.previews }, this.previews()))));



  } });


const app = document.getElementById('app');
render( /*#__PURE__*/React.createElement(FileUpload, { multiple: true,
  name: "example-upload",
  maxSize: 300000,
  onUpload: uploadFileToServer,
  label: "Upload Files" }), app);

That’s all! hopefully, you have successfully integrated this drag-and-drop file uploader with preview and delete into your project. If you have any questions or suggestions, feel free to comment below.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply