{"id":135274,"date":"2025-11-10T07:49:28","date_gmt":"2025-11-10T07:49:28","guid":{"rendered":"https:\/\/phppot.com\/?p=24562"},"modified":"2025-11-10T07:49:28","modified_gmt":"2025-11-10T07:49:28","slug":"react-file-upload-with-preview-and-drag-and-drop-support","status":"publish","type":"post","link":"https:\/\/sickgaming.net\/blog\/2025\/11\/10\/react-file-upload-with-preview-and-drag-and-drop-support\/","title":{"rendered":"React File Upload with Preview and Drag-and-Drop Support"},"content":{"rendered":"<div class=\"modified-on\" readability=\"7.1489361702128\"> by <a href=\"https:\/\/phppot.com\/about\/\">Vincy<\/a>. Last modified on November 20th, 2025.<\/div>\n<p>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.<\/p>\n<p>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.<\/p>\n<ol>\n<li><a href=\"https:\/\/phppot.com\/react\/react-drag-and-drop\/\">Drag and drop<\/a><\/li>\n<li>File validation<\/li>\n<li>Uploading to backend<\/li>\n<li>Saving file path to database<\/li>\n<li>Preview after upload<\/li>\n<li>Error handling<\/li>\n<\/ol>\n<p>It is easy to integrate into any React application, since it is structured with separate components for the upload and preview UI.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-large wp-image-24703\" src=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-preview-drag-drop-550x288.jpeg\" alt=\"React File Upload Preview Drag Drop\" width=\"550\" height=\"288\" srcset=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-preview-drag-drop-550x288.jpeg 550w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-preview-drag-drop-300x157.jpeg 300w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-preview-drag-drop-768x402.jpeg 768w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-preview-drag-drop.jpeg 1200w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\"><\/p>\n<h2>React UI with file upload interface<\/h2>\n<p>Two React components created for this file upload UI. Those are UploadBox and FilePreview.<\/p>\n<p>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.<\/p>\n<p>The FileUpload JSX handles the following processes before uploading a file.<\/p>\n<ol>\n<li><a href=\"https:\/\/phppot.com\/jquery\/file-size-validation-using-jquery\/\">File validation<\/a> about its extension and size.<\/li>\n<li>Handling errors or success acknowledgement for UI.<\/li>\n<li>Preparing form data with the file binaries.<\/li>\n<\/ol>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-large wp-image-24564\" src=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-empty-state-550x155.jpg\" alt=\"react file upload empty state\" width=\"550\" height=\"155\" srcset=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-empty-state-550x155.jpg 550w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-empty-state-300x84.jpg 300w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-empty-state-768x216.jpg 768w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-empty-state.jpg 1199w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\"><\/p>\n<p class=\"code-heading\"><code>src\/components\/FileUpload.jsx<\/code><\/p>\n<pre class=\"prettyprint\"><code class=\"language-jsx\">import { useState } from \"react\";\nimport axios from \"axios\";\nimport SERVER_SIDE_API_ROOT from \"..\/..\/config\";\nimport FilePreview from \".\/FilePreview\";\nimport UploadBox from \".\/UploadBox\";\nimport \"..\/..\/public\/assets\/css\/style.css\";\nconst FileUpload = () =&gt; { 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) =&gt; { setErrorMsg(\"\"); const safeFiles = []; const rejectedFiles = []; fileList.forEach((file) =&gt; { const ext = file.name.split(\".\").pop().toLowerCase(); if (!allowedExtensions.includes(ext)) return rejectedFiles.push(\"Invalid file type\"); if (file.size &gt; MAX_FILE_SIZE) return rejectedFiles.push(\"Maximum file size is 2MB.\"); if (file.size &lt;= 0) return rejectedFiles.push(\"Empty file\"); safeFiles.push(file); }); if (rejectedFiles.length &gt; 0) { setErrorMsg(rejectedFiles[0]); setUploading(false); return; } if (!safeFiles.length) return; const formData = new FormData(); safeFiles.forEach((file) =&gt; formData.append(\"files[]\", file)); setUploading(true); const delay = new Promise((resolve) =&gt; 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) =&gt; f.status === \"uploaded\"); setFiles((prev) =&gt; [...prev, ...uploadedFiles.map(f =&gt; safeFiles.find(sf =&gt; sf.name === f.name))]); } catch { setErrorMsg(\"Server error \u2014 please try again later.\"); } setUploading(false); }; const handleDrop = async (e) =&gt; { e.preventDefault(); setDragActive(false); await uploadFiles(Array.from(e.dataTransfer.files)); }; return ( &lt;div className=\"upload-wrapper\"&gt; &lt;UploadBox dragActive={dragActive} uploading={uploading} errorMsg={errorMsg} handleDrop={handleDrop} setDragActive={setDragActive} &gt; &lt;\/UploadBox&gt; &lt;FilePreview files={files} uploading={uploading} \/&gt; &lt;\/div&gt; );\n};\nexport default FileUpload;\n<\/code><\/pre>\n<h2>PHP file upload endpoint<\/h2>\n<p>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 <a href=\"https:\/\/phppot.com\/php\/php-random-string\/\">unique random id<\/a>.<\/p>\n<p>Once the PHP move_uploaded_file() saves the files to the directory, this code <a href=\"https:\/\/phppot.com\/php\/php-upload-image-to-database\/\">inserts the target path to the database<\/a>.<\/p>\n<p class=\"code-heading\"><code>drag-drop-file-upload-api\/file-upload.php<\/code><\/p>\n<pre class=\"prettyprint\"><code class=\"language-php\">&lt;?php\nheader(\"Access-Control-Allow-Origin: *\");\nheader(\"Access-Control-Allow-Methods: POST, GET, OPTIONS\");\nheader(\"Access-Control-Allow-Headers: Content-Type\");\ninclude \"db.php\";\n$uploadDir = \"uploads\/\";\n$response = [];\nif (!file_exists($uploadDir)) { mkdir($uploadDir, 0777, true);\n}\n$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'doc', 'docx'];\n$maxFileSize = 2 * 1024 * 1024; foreach ($_FILES['files']['name'] as $key =&gt; $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\" =&gt; $name, \"status\" =&gt; \"blocked\", \"message\" =&gt; \"File type not allowed (.{$extension})\" ]; continue; } if ($size &gt; $maxFileSize) { $response[] = [ \"name\" =&gt; $name, \"status\" =&gt; \"blocked\", \"message\" =&gt; \"Maximum file size is 2M.\" ]; continue; } if ($size &lt;= 0) { $response[] = [ \"name\" =&gt; $name, \"status\" =&gt; \"blocked\", \"message\" =&gt; \"Empty file\" ]; continue; } $uniqueName = uniqid() . \"_\" . basename($name); $targetPath = $uploadDir . $uniqueName; if (move_uploaded_file($tmpName, $targetPath)) { $stmt = $conn-&gt;prepare(\"INSERT INTO uploaded_files (file_name, file_path) VALUES (?, ?)\"); $stmt-&gt;bind_param(\"ss\", $uniqueName, $targetPath); $stmt-&gt;execute(); $response[] = [ \"name\" =&gt; $name, \"path\" =&gt; $targetPath, \"status\" =&gt; \"uploaded\" ]; } else { $response[] = [ \"name\" =&gt; $name, \"status\" =&gt; \"failed\", \"message\" =&gt; \"Error moving uploaded file.\" ]; }\n}\necho json_encode([\"success\" =&gt; true, \"files\" =&gt; $response]);\n?&gt;\n<\/code><\/pre>\n<h2>Drop area to place the dragged files<\/h2>\n<p>It contains UI elements to define the file drop area. The drop box uses onDragOver callback to highlight the drop area on hover.<\/p>\n<p>And, the onDrop callback prepares the form data to post the dropped file binary to the server.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-large wp-image-24578\" src=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-error-state-550x197.jpg\" alt=\"react file upload error state\" width=\"550\" height=\"197\" srcset=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-error-state-550x197.jpg 550w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-error-state-300x107.jpg 300w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-error-state-768x275.jpg 768w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-error-state.jpg 1215w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\"><\/p>\n<p class=\"code-heading\"><code>src\/components\/UploadBox.jsx<\/code><\/p>\n<pre class=\"prettyprint\"><code class=\"language-jsx\">const UploadBox = ({ dragActive, uploading, errorMsg, handleDrop, setDragActive }) =&gt; ( &lt;&gt; &lt;div className={`upload-box ${dragActive ? \"active\" : \"\"}`} onDragOver={(e) =&gt; { e.preventDefault(); setDragActive(true); }} onDragLeave={() =&gt; setDragActive(false)} onDrop={handleDrop} &gt; &lt;h3 className=\"upload-title\"&gt;Drag &amp; Drop Files Here&lt;\/h3&gt; &lt;p className=\"upload-text\"&gt;Files will upload automatically&lt;\/p&gt; &lt;\/div&gt; {uploading &amp;&amp; &lt;p className=\"uploading-text\"&gt;Uploading...&lt;\/p&gt;} {errorMsg &amp;&amp; &lt;p className=\"error-text\"&gt;{errorMsg}&lt;\/p&gt;} &lt;\/&gt;\n); export default UploadBox;\n<\/code><\/pre>\n<h2>Showing file preview with thumbnails<\/h2>\n<p>The FilePreview component displays the uploaded files in a list format. It will show its thumbnail, name and size.<\/p>\n<p>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.<br \/><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-large wp-image-24698\" src=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-success-case-output-550x421.jpg\" alt=\"React File Upload Success Case Output\" width=\"550\" height=\"421\" srcset=\"https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-success-case-output-550x421.jpg 550w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-success-case-output-300x230.jpg 300w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-success-case-output-768x588.jpg 768w, https:\/\/phppot.com\/wp-content\/uploads\/2025\/11\/react-file-upload-success-case-output.jpg 1167w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\"><\/p>\n<p class=\"code-heading\"><code>src\/components\/FilePreview.jsx<\/code><\/p>\n<pre class=\"prettyprint\"><code class=\"language-jsx\">const FilePreview = ({ files, uploading }) =&gt; { if (!files.length) return null; return ( &lt;div className=\"preview-list\"&gt; {files.map((file, i) =&gt; ( &lt;div key={i} className=\"preview-row\"&gt; {file.type?.startsWith(\"image\/\") ? ( &lt;div className=\"preview-thumb-wrapper\"&gt; &lt;img src={URL.createObjectURL(file)} alt={file.name} className={`preview-thumb ${uploading ? \"blurred\" : \"\"}`} \/&gt; {uploading &amp;&amp; ( &lt;div className=\"preview-loader\"&gt; &lt;img src=\"\/assets\/image\/loader.svg\" alt=\"Loading...\" \/&gt; &lt;\/div&gt; )} &lt;\/div&gt; ) : ( &lt;div className=\"file-icon\"&gt;&lt;\/div&gt; )} &lt;div className=\"file-info\"&gt; &lt;p className=\"file-name\"&gt;{file.name}&lt;\/p&gt; &lt;p className=\"file-size\"&gt;{Math.round(file.size \/ 1024)} KB&lt;\/p&gt; &lt;\/div&gt; &lt;\/div&gt; ))} &lt;\/div&gt; );\n};\nexport default FilePreview;\n<\/code><\/pre>\n<h2>How to set up this application<\/h2>\n<p>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.<\/p>\n<ol>\n<li>Download the source and unzip into your computer.<\/li>\n<li>Copy the <code>drag-drop-file-upload-api<\/code> into the PHP web root.<\/li>\n<li>Create a database <code>file_upload_db<\/code> and import the SQL script in the <code>drag-drop-file-upload-api\/sql<\/code><\/li>\n<li>Configure database details with <code>db.php<\/code><\/li>\n<li>Configure the PHP endpoint URL in React in <code>src\/config.js<\/code><\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>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.<\/p>\n<p><strong>References:<\/strong><\/p>\n<ol>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTML_Drag_and_Drop_API\" target=\"_blank\" rel=\"noopener\">HTML drag and drop UI<\/a><\/li>\n<li><a href=\"https:\/\/axios-http.com\/docs\/req_config\" target=\"_blank\" rel=\"noopener\">Axios API request config option<\/a><\/li>\n<\/ol>\n<p><a class=\"download\" href=\"https:\/\/phppot.com\/downloads\/react\/react-file-upload-preview-drag-drop.zip\">Download<\/a><\/p>\n<div class=\"written-by\" readability=\"9.8427672955975\">\n<div class=\"author-photo\"> <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/phppot.com\/wp-content\/themes\/solandra\/images\/Vincy.jpg\" alt=\"Vincy\" width=\"100\" height=\"100\" title=\"Vincy\"> <\/div>\n<div class=\"written-by-desc\" readability=\"14.764150943396\"> Written by <a href=\"https:\/\/phppot.com\/about\/\">Vincy<\/a>, 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. <\/div>\n<\/p><\/div>\n<p> <!-- #comments --> <\/p>\n<div class=\"related-articles\">\n<h2>Related Tutorials<\/h2>\n<\/p><\/div>\n<p> <a href=\"https:\/\/phppot.com\/react\/react-file-upload-preview-drag-drop\/#top\" class=\"top\">\u2191 Back to Top<\/a> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":135275,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[65],"tags":[],"class_list":["post-135274","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php-updates"],"_links":{"self":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/135274","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/comments?post=135274"}],"version-history":[{"count":0,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/135274\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media\/135275"}],"wp:attachment":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media?parent=135274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/categories?post=135274"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/tags?post=135274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}