React File Upload with Preview and Drag-and-Drop Support

by Vincy. Last modified on November 20th, 2025.

This example contains a React drag-and-drop file upload with file type and size validation. It connects backend to upload files to the server via Axios.

The workflow allows users to drop files to upload and shows file preview below the drop area. This file upload example includes the following featured functionalities.

  1. Drag and drop
  2. File validation
  3. Uploading to backend
  4. Saving file path to database
  5. Preview after upload
  6. Error handling

It is easy to integrate into any React application, since it is structured with separate components for the upload and preview UI.

React File Upload Preview Drag Drop

React UI with file upload interface

Two React components created for this file upload UI. Those are UploadBox and FilePreview.

The UploadBox is the drop area for dragged files to be uploaded. Once upload completed, file thumbnails are shown in a preview box by using the FilePreview component.

The FileUpload JSX handles the following processes before uploading a file.

  1. File validation about its extension and size.
  2. Handling errors or success acknowledgement for UI.
  3. Preparing form data with the file binaries.

react file upload empty state

src/components/FileUpload.jsx

import { useState } from "react";
import axios from "axios";
import SERVER_SIDE_API_ROOT from "../../config";
import FilePreview from "./FilePreview";
import UploadBox from "./UploadBox";
import "../../public/assets/css/style.css";
const FileUpload = () => {
  const [files, setFiles] = useState([]);
  const [dragActive, setDragActive] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [errorMsg, setErrorMsg] = useState("");
  const allowedExtensions = ["jpg", "jpeg", "png", "gif", "pdf", "doc", "docx", "txt"];
  const MAX_FILE_SIZE = 2 * 1024 * 1024;

  const uploadFiles = async (fileList) => {
    setErrorMsg("");
    const safeFiles = [];
    const rejectedFiles = [];

    fileList.forEach((file) => {
      const ext = file.name.split(".").pop().toLowerCase();
      if (!allowedExtensions.includes(ext)) return rejectedFiles.push("Invalid file type");
      if (file.size > MAX_FILE_SIZE) return rejectedFiles.push("Maximum file size is 2MB.");
      if (file.size <= 0) return rejectedFiles.push("Empty file");
      safeFiles.push(file);
    });
    if (rejectedFiles.length > 0) {
      setErrorMsg(rejectedFiles[0]);
      setUploading(false);
      return;
    }
    if (!safeFiles.length) return;
    const formData = new FormData();
    safeFiles.forEach((file) => formData.append("files[]", file));
    setUploading(true);

    const delay = new Promise((resolve) => setTimeout(resolve, 800));
    try {
      const res = await Promise.all([
        axios.post(`${SERVER_SIDE_API_ROOT}/file-upload.php`, formData, {
          headers: { "Content-Type": "multipart/form-data" },
        }),
        delay,
      ]);
      const uploadedFiles = res[0].data.files.filter((f) => f.status === "uploaded");
      setFiles((prev) => [...prev, ...uploadedFiles.map(f => safeFiles.find(sf => sf.name === f.name))]);
    } catch {
      setErrorMsg("Server error — please try again later.");
    }
    setUploading(false);
  };
  const handleDrop = async (e) => {
    e.preventDefault();
    setDragActive(false);
    await uploadFiles(Array.from(e.dataTransfer.files));
  };
  return (
    <div className="upload-wrapper">
      <UploadBox
        dragActive={dragActive}
        uploading={uploading}
        errorMsg={errorMsg}
        handleDrop={handleDrop}
        setDragActive={setDragActive}
      >
      </UploadBox>
      <FilePreview files={files} uploading={uploading} />
    </div>
  );
};
export default FileUpload;

PHP file upload endpoint

The PHP script validates the received file binary before uploading to the server directory. If the validation passes, this script give name to the file with a unique random id.

Once the PHP move_uploaded_file() saves the files to the directory, this code inserts the target path to the database.

drag-drop-file-upload-api/file-upload.php

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
include "db.php";
$uploadDir = "uploads/";
$response = [];
if (!file_exists($uploadDir)) {
    mkdir($uploadDir, 0777, true);
}
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'doc', 'docx'];
$maxFileSize = 2 * 1024 * 1024; 
foreach ($_FILES['files']['name'] as $key => $name) {
    $tmpName = $_FILES['files']['tmp_name'][$key];
    $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    $size = $_FILES['files']['size'][$key];
    if (!in_array($extension, $allowedExtensions)) {
        $response[] = [
            "name" => $name,
            "status" => "blocked",
            "message" => "File type not allowed (.{$extension})"
        ];
        continue;
    }
    if ($size > $maxFileSize) {
        $response[] = [
            "name" => $name,
            "status" => "blocked",
            "message" => "Maximum file size is 2M."
        ];
        continue;
    }
    if ($size <= 0) {
        $response[] = [
            "name" => $name,
            "status" => "blocked",
            "message" => "Empty file"
        ];
        continue;
    }
    $uniqueName = uniqid() . "_" . basename($name);
    $targetPath = $uploadDir . $uniqueName;

    if (move_uploaded_file($tmpName, $targetPath)) {
        $stmt = $conn->prepare("INSERT INTO uploaded_files (file_name, file_path) VALUES (?, ?)");
        $stmt->bind_param("ss", $uniqueName, $targetPath);
        $stmt->execute();
        $response[] = [
            "name" => $name,
            "path" => $targetPath,
            "status" => "uploaded"
        ];
    } else {
        $response[] = [
            "name" => $name,
            "status" => "failed",
            "message" => "Error moving uploaded file."
        ];
    }
}
echo json_encode(["success" => true, "files" => $response]);
?>

Drop area to place the dragged files

It contains UI elements to define the file drop area. The drop box uses onDragOver callback to highlight the drop area on hover.

And, the onDrop callback prepares the form data to post the dropped file binary to the server.

react file upload error state

src/components/UploadBox.jsx

const UploadBox = ({ dragActive, uploading, errorMsg, handleDrop, setDragActive }) => (
  <>
    <div
      className={`upload-box ${dragActive ? "active" : ""}`}
      onDragOver={(e) => {
        e.preventDefault();
        setDragActive(true);
      }}
      onDragLeave={() => setDragActive(false)}
      onDrop={handleDrop}
      >
      <h3 className="upload-title">Drag & Drop Files Here</h3>
      <p className="upload-text">Files will upload automatically</p>
    </div>
    {uploading && <p className="uploading-text">Uploading...</p>}
    {errorMsg && <p className="error-text">{errorMsg}</p>}
 </>
);

export default UploadBox;

Showing file preview with thumbnails

The FilePreview component displays the uploaded files in a list format. It will show its thumbnail, name and size.

If an image upload, the preview will show the image thumbnail. It a document type file is uploaded, the default icon is shown to the preview screen.
React File Upload Success Case Output

src/components/FilePreview.jsx

const FilePreview = ({ files, uploading }) => {
  if (!files.length) return null;
  return (
    <div className="preview-list">
      {files.map((file, i) => (
        <div key={i} className="preview-row">
          {file.type?.startsWith("image/") ? (
            <div className="preview-thumb-wrapper">
              <img
                src={URL.createObjectURL(file)}
                alt={file.name}
                className={`preview-thumb ${uploading ? "blurred" : ""}`}
              />
              {uploading && ( <div className="preview-loader">
                <img src="/assets/image/loader.svg" alt="Loading..." />
               </div>
              )}
            </div>
          ) : (
            <div className="file-icon"></div>
          )}
          <div className="file-info">
            <p className="file-name">{file.name}</p>
            <p className="file-size">{Math.round(file.size / 1024)} KB</p>
          </div>
        </div>
      ))}
    </div>
  );
};
export default FilePreview;

How to set up this application

The below steps help to set up this example to run in your environment. After these steps, start the npm dev server and run the React drag and drop app.

  1. Download the source and unzip into your computer.
  2. Copy the drag-drop-file-upload-api into the PHP web root.
  3. Create a database file_upload_db and import the SQL script in the drag-drop-file-upload-api/sql
  4. Configure database details with db.php
  5. Configure the PHP endpoint URL in React in src/config.js

Conclusion

I hope the React code provides a modern file upload interface. The drag-and-drop, file validation, preview rendering and database insert is a stack of features enriches the example code. This code well-structured and ready to integrate with an application easily. If you want any add-on feature to this example, please let me know.

References:

  1. HTML drag and drop UI
  2. Axios API request config option

Download

Vincy
Written by Vincy, a web developer with 15+ years of experience and a Masters degree in Computer Science. She specializes in building modern, lightweight websites using PHP, JavaScript, React, and related technologies. Phppot helps you in mastering web development through over a decade of publishing quality tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top

Share this page