React 2 min read 666 views

How to Implement File Upload with Progress Bar in React

Build a modern file upload component with drag-and-drop support and real-time progress tracking.

E
File upload interface

The Component

import { useState, useCallback } from 'react';

export default function FileUpload({ onUpload }) {
    const [progress, setProgress] = useState(0);
    const [isDragging, setIsDragging] = useState(false);
    const [file, setFile] = useState(null);

    const handleDrop = useCallback((e) => {
        e.preventDefault();
        setIsDragging(false);

        const droppedFile = e.dataTransfer.files[0];
        if (droppedFile) {
            setFile(droppedFile);
            uploadFile(droppedFile);
        }
    }, []);

    const uploadFile = async (file) => {
        const formData = new FormData();
        formData.append('file', file);

        const xhr = new XMLHttpRequest();

        xhr.upload.onprogress = (e) => {
            if (e.lengthComputable) {
                const percent = Math.round((e.loaded / e.total) * 100);
                setProgress(percent);
            }
        };

        xhr.onload = () => {
            if (xhr.status === 200) {
                const response = JSON.parse(xhr.responseText);
                onUpload?.(response);
            }
            setProgress(0);
        };

        xhr.onerror = () => {
            alert('Upload failed');
            setProgress(0);
        };

        xhr.open('POST', '/api/upload');
        xhr.send(formData);
    };

    return (
        <div
            onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
            onDragLeave={() => setIsDragging(false)}
            onDrop={handleDrop}
            className={`border-2 border-dashed rounded-lg p-8 text-center
                ${isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
        >
            <input
                type="file"
                onChange={(e) => {
                    const f = e.target.files[0];
                    if (f) { setFile(f); uploadFile(f); }
                }}
                className="hidden"
                id="file-input"
            />

            <label htmlFor="file-input" className="cursor-pointer">
                <p>Drag & drop or click to upload</p>
            </label>

            {progress > 0 && (
                <div className="mt-4">
                    <div className="bg-gray-200 rounded-full h-2">
                        <div
                            className="bg-blue-500 h-2 rounded-full transition-all"
                            style={{ width: `${progress}%` }}
                        />
                    </div>
                    <p className="mt-2">{progress}%</p>
                </div>
            )}
        </div>
    );
}

Laravel Backend

public function upload(Request $request)
{
    $request->validate([
        'file' => 'required|file|max:10240', // 10MB
    ]);

    $path = $request->file('file')->store('uploads', 'public');

    return response()->json([
        'path' => $path,
        'url' => Storage::url($path),
    ]);
}
Share this article:
ES

Written by Edrees Salih

Full-stack software engineer with 9 years of experience. Passionate about building scalable solutions and sharing knowledge with the developer community.

View Profile

Comments (0)

Leave a Comment

Your email will not be published.

No comments yet. Be the first to share your thoughts!