35 - Drag-and-Drop File Upload

Description

Create a React component supporting direct file uploads through drag-and-drop functionality.

  • Allows users to drag and drop files onto a designated area.

  • Displays visual cues to indicate drag-and-drop support and file selection.

  • Handles file selection and initiates the upload process.

  • Provides feedback during the upload process (progress bars, success/error messages).

  • Supports multiple file uploads and potential cancellation.

Algorithm

  1. Component Structure:

    • Define a functional component named DragAndDropFileUpload.

    • Accept props for:

      • onFilesUploaded: Function to handle uploaded files.

      • allowedFileTypes: Array of allowed file extensions (optional).

    • Maintain internal state for:

      • selectedFiles: Array of selected files.

      • isDraggingOver: Boolean indicating if a file is being dragged over the area.

      • uploadProgress: Object tracking upload progress for each file (optional).

  2. Drag-and-Drop Handling:

    • Attach event listeners to the drop area:

      • onDragOver: Prevent default behavior and indicate drag-and-drop support.

      • onDragEnter: Update isDraggingOver state to show visual cues.

      • onDragLeave: Update isDraggingOver state to remove visual cues.

      • onDrop: Handle file selection, update selectedFiles state, and initiate uploads.

  3. File Upload Handling:

    • Use JavaScript's FileReader API or a library for advanced features.

    • Iterate through selectedFiles:

      • Check for allowed file types if specified.

      • Initiate upload using a library like axios or the browser's fetch API.

      • Track upload progress (optional).

      • Handle upload completion or failure, providing feedback to the user.

  4. Rendering:

    • Render a drop area (usually a div) with visual cues based on isDraggingOver state.

    • Display selected file information (names, sizes, previews).

    • Show upload progress indicators or feedback messages.

Code

import React, { useState, useEffect, useRef } from 'react';

const DragAndDropFileUpload = ({ onFilesUploaded, allowedFileTypes }) => {
  const [selectedFiles, setSelectedFiles] = useState([]);
  const [isDraggingOver, setIsDraggingOver] = useState(false);
  const uploadProgressRef = useRef({});

  const handleDragOver = (event) => {
    event.preventDefault();
    setIsDraggingOver(true);
  };

  const handleDragEnter = () => setIsDraggingOver(true);

  const handleDragLeave = () => setIsDraggingOver(false);

  const handleDrop = (event) => {
    event.preventDefault();
    setIsDraggingOver(false);

    const droppedFiles = event.dataTransfer.files;
    const validFiles = [];

    for (const file of droppedFiles) {
      if (!allowedFileTypes || allowedFileTypes.includes(file.type.split('/')[1])) {
        validFiles.push(file);
      }
    }

    setSelectedFiles(validFiles);
    startUploads(validFiles);
  };

  const startUploads = (files) => {
    files.forEach((file) => {
      uploadProgressRef.current[file.name] = 0;
      const reader = new FileReader();
      reader.onprogress = (event) => {
        if (event.loaded) {
          uploadProgressRef.current[file.name] = event.loaded / file.size * 100;
          // Update UI to reflect progress based on uploadProgressRef
        }
      };
      reader.onloadend = () => {
        // Send file data to your backend using desired approach (e.g., axios)
        // Handle upload success/failure and update UI accordingly
      };
      reader.readAsArrayBuffer(file);
    });
  };

  useEffect(() => {
    onFilesUploaded(selectedFiles);
  }, [selectedFiles]);

  return (
    <div
      className="drag-and-drop-area"
      onDragOver={handleDragOver}
      onDragEnter={handleDragEnter}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      style={{
        border: isDraggingOver ? '2px dashed #ccc' : '2px solid #ddd',
        padding: '20px',
      }}
    >
      {isDraggingOver && <p>Drop your files here!</p>}
      {selectedFiles.length > 0 && (
        <ul>
          {selectedFiles.map((file) => (
            <li key={file.name}>
              {file.name}
              {uploadProgressRef.current[file.name] > 0 && (
                <span> - {uploadProgressRef.current[file.name].toFixed(2)}% uploaded</span>
              )}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default DragAndDropFileUpload;

Explanation

  1. State Management: Tracks selected files, drag state, and upload progress for each file.

  2. Drag-and-Drop Handling: Listens to events on the drop area and updates state accordingly.

  3. File Upload Handling: Reads selected files, validates against allowed types, and initiates uploads using FileReader and an upload library (not included).

  4. Progress Tracking: Updates progress based on FileReader events and reflects it in the UI.

  5. Feedback and Upload completion: Provides visual cues for file selection and progress. Handles upload completion and errors based on your backend implementation.

Additional Notes

  • This is a basic example and can be customized further, like adding animations, styling, and progress bars.

  • Consider accessibility for keyboard navigation and screen reader compatibility.

  • Integrate with your chosen upload library and backend code for actual file transmission.

Last updated