Problem Statement
Build a file upload box. The user picks a file or drops it on the box, sees its details, uploads it, and sees the upload progress and the result.
Functional Requirements
- Pick or drop: Clicking the dashed box opens the file picker. Dropping a file on it also selects it. Only the first file is used.
- Drag feedback: The box turns blue while a file is dragged over it.
- File details: Show the selected file's name, size in KB and type, or
Unknownif the type is empty. - Upload: The Upload button posts the file as
FormDataunder thefilekey tohttps://httpbin.org/postwith axios. - Progress: While uploading, hide the button and show a bar and a percent from
onUploadProgress. - Result: Show a success or an error message when the request ends. Picking a new file clears it.
Technical Constraints
- Written in TypeScript. Upload state is one of
"idle" | "uploading" | "success" | "error". - One file at a time. No type or size checks and no cancel.
Overview
A React component that handles file uploads with progress tracking, status management, and drag-and-drop functionality.
Core Functionality
State Management
const [file, setFile] = useState<File | null>(null);
const [status, setStatus] = useState<UploadStatus>("idle");
const [uploadProgress, setUploadProgress] = useState(0);
const [dragActive, setDragActive] = useState(false);
type UploadStatus = "idle" | "uploading" | "success" | "error";file: Stores the selected file objectstatus: Tracks upload state ("idle", "uploading", "success", "error")uploadProgress: Tracks upload progress (0-100)dragActive: Manages drag-and-drop visual feedback
Key Functions Explained
1. File Selection Handler
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFile(e.target.files[0]);
setStatus("idle");
}
};- Triggered when user selects a file via input
e.target.filescontains FileList object- We store the first file (files[0])
- Status goes back to
idle, so an old success or error message does not show for the new file
2. Drag and Drop Handlers
const handleDrag = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(e.type === "dragenter" || e.type === "dragover");
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files?.[0]) {
setFile(e.dataTransfer.files[0]);
setStatus("idle");
}
};handleDrag: Manages drag state for visual feedbackhandleDrop: Processes dropped filee.dataTransfer.filescontains dropped files
3. File Upload Handler
const handleFileUpload = async () => {
if (!file) return null;
setStatus("uploading");
setUploadProgress(0);
const formData = new FormData();
formData.append("file", file);
try {
const response = await axios.post(URL, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
const progress = progressEvent?.total
? Math.round((progressEvent.loaded * 100) / progressEvent.total)
: 0;
setUploadProgress(progress);
},
});
setStatus("success");
} catch (error) {
setStatus("error");
}
};Deep Dive into Key Concepts
FormData
const formData = new FormData();
formData.append("file", file);FormDatais a built-in browser API- Used to construct a set of key/value pairs representing form fields
- Automatically sets proper headers for file upload
Example:
// Creating FormData
const formData = new FormData();
// Adding different types of data
formData.append("file", fileObject);
formData.append("username", "john_doe");
formData.append("age", "25");
// You can also append multiple files
formData.append("documents", file1);
formData.append("documents", file2);onUploadProgress
onUploadProgress: (progressEvent) => {
const progress = progressEvent?.total
? Math.round((progressEvent.loaded * 100) / progressEvent.total)
: 0;
setUploadProgress(progress);
};- Axios-specific callback for tracking upload progress
progressEvent.loaded: Bytes transferredprogressEvent.total: Total bytes to transfer- Calculates percentage: (loaded / total) * 100
Example of Progress Event:
// Sample progressEvent object
{
loaded: 25000, // bytes uploaded
total: 100000, // total file size in bytes
progress: 0.25, // 25% complete
bytes: 25000, // same as loaded
rate: 256000, // bytes per second
estimated: 2, // seconds remaining
}Code Flow
-
Initial State
- Component starts with null file and idle status
-
File Selection
- User selects file via click or drag-and-drop
- File stored in state
- File details displayed
-
Upload Process
Click Upload Button ↓ Create FormData ↓ Start Upload (Status: uploading) ↓ Track Progress (onUploadProgress) ↓ Handle Response ↓ Update Status (success/error) -
Status Management
idle: Initial state or ready to uploaduploading: During file transfersuccess: Upload completederror: Upload failed
Error Handling
try {
// upload logic
} catch (error) {
setStatus("error");
setUploadProgress(0);
}- Catches network errors
- Catches server errors
- Resets progress
- Updates UI with error state
Common Interview Questions
"How would you handle multiple file uploads?"
Keep an array of files in state and read every selected file. Add
multipleto the input.const [files, setFiles] = useState<File[]>([]); const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { if (e.target.files) { setFiles(Array.from(e.target.files)); } };
"How do you validate file types?"
Check
file.typeagainst an allow list before uploading. Also setaccepton the input so the picker filters files, but do not rely on it alone.const allowedTypes = ["image/jpeg", "image/png"]; const validateFile = (file: File) => { return allowedTypes.includes(file.type); };
"How do you handle large file uploads?"
Reject files over a size limit first. For really large files, upload in chunks so a failed chunk can be retried and the upload can resume.
const MAX_SIZE = 5 * 1024 * 1024; // 5MB if (file.size > MAX_SIZE) { alert("File too large"); return; }
"How do you cancel an upload in progress?"
Pass an
AbortControllersignal to axios and callabort()from a Cancel button. Axios deprecatedCancelToken, so use the signal instead.const controller = new AbortController(); axios.post(URL, formData, { signal: controller.signal }); // To cancel controller.abort();