Drag-and-Drop File Upload Component with React and TypeScript

Updated on · 4 min read
Drag-and-Drop File Upload Component with React and TypeScript

File uploads are a common feature in many web applications, and making the process user-friendly is key to providing a good user experience. One way to improve this process is by implementing a drag-and-drop interface, which allows users to select files from their file system and easily drop them into your application.

In an earlier post, we discussed how to handle file uploads with React Hook Form. Building upon that knowledge, in this tutorial, we're going to take a step further and demonstrate how to construct a drag-and-drop file upload component in React, leveraging TypeScript for enhanced type safety and robustness. We won't rely on any external libraries, giving you the fundamental knowledge and freedom to create and customize a file upload solution that fits your specific needs.

By the end of this tutorial, you'll be well-equipped to create a robust, user-friendly drag-and-drop file upload component that you can customize to fit the needs of your applications.

Understanding HTML5 Drag and Drop API

The HTML5 Drag and Drop API allows developers to make their elements draggable and accept dropped items. This functionality is natively provided by the web browser and doesn't require any external libraries. Here are the key events and methods we'll use to implement our feature:

  • dragover: This event is fired when a dragged item is being moved over a valid drop target. By default, data/elements cannot be dropped in other elements. To allow a drop, we must prevent the default handling of the element using event.preventDefault() in our onDragOver event handler.

  • drop: This event is fired when a dragged item is dropped on a valid drop target. Here again, to change the default behavior, we need to prevent the default handling using event.preventDefault().

  • dragleave: This event is fired when a dragged item leaves a valid drop target. This can be used to revert any changes you made on dragover.

  • DataTransfer object: The DataTransfer object is used to hold the data that is being dragged during a drag-and-drop operation. It can hold one or more data items in different formats. In our case, we'll use it to hold the file data being uploaded. This object is available as event.dataTransfer in all drag events.

These are the building blocks of any drag-and-drop operation. In the next section, we will see how to utilize these to create a drag-and-drop file upload component in React.

The HTML5 Drag and Drop API includes two additional events, dragstart and dragend, typically used for controlling the lifecycle of drag operations within a web application. They aren't used in this tutorial as we're handling files from the user's system, not initiating or ending drag operations within our application.

Building the Drag-and-Drop Component

To create our drag-and-drop component, we'll create a new component named FileDrop. Let's start with defining the necessary drop events.

tsx
import { DragEvent } from "react"; export function FileDrop() { // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); // Here we'll handle the dropped files }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} > Drag and drop some files here </div> ); }
tsx
import { DragEvent } from "react"; export function FileDrop() { // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); // Here we'll handle the dropped files }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} > Drag and drop some files here </div> ); }

We use event.preventDefault() in each of these handlers to prevent the browser's default behavior. The handleDrop function will later be updated to handle the dropped files.

Lastly, we'll add some styles to provide visual feedback when a user drags a file over the drop zone.

tsx
import { DragEvent, useState } from "react"; export function FileDrop() { const [dragIsOver, setDragIsOver] = useState(false); // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(true); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(false); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(false); }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px", width: "300px", border: "1px dotted", backgroundColor: dragIsOver ? "lightgray" : "white", }} > Drag and drop some files here </div> ); }
tsx
import { DragEvent, useState } from "react"; export function FileDrop() { const [dragIsOver, setDragIsOver] = useState(false); // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(true); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(false); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setDragIsOver(false); }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px", width: "300px", border: "1px dotted", backgroundColor: dragIsOver ? "lightgray" : "white", }} > Drag and drop some files here </div> ); }

Here, we use a piece of state dragIsOver to determine whether a file is currently being dragged over the drop zone. This state is used to change the background color of the drop zone. The result is a very basic file upload area.

File drop area

The exact style and colors should be adjusted to fit your application's theme and design.

Reading Dropped Files in TypeScript

With our drag-and-drop event handlers in place, we now need to handle the files that the user drops. To do this, we'll utilize the HTML5 FileReader API. Let's update the handleDrop function to read the dropped files.

tsx
import { DragEvent, useState } from "react"; export function FileDrop() { const [isOver, setIsOver] = useState(false); const [files, setFiles] = useState<File[]>([]); // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(true); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(false); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(false); // Fetch the files const droppedFiles = Array.from(event.dataTransfer.files); setFiles(droppedFiles); // Use FileReader to read file content droppedFiles.forEach((file) => { const reader = new FileReader(); reader.onloadend = () => { console.log(reader.result); }; reader.onerror = () => { console.error("There was an issue reading the file."); }; reader.readAsDataURL(file); return reader; }); }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px", width: "300px", border: "1px dotted", backgroundColor: isOver ? "lightgray" : "white", }} > Drag and drop some files here </div> ); }
tsx
import { DragEvent, useState } from "react"; export function FileDrop() { const [isOver, setIsOver] = useState(false); const [files, setFiles] = useState<File[]>([]); // Define the event handlers const handleDragOver = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(true); }; const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(false); }; const handleDrop = (event: DragEvent<HTMLDivElement>) => { event.preventDefault(); setIsOver(false); // Fetch the files const droppedFiles = Array.from(event.dataTransfer.files); setFiles(droppedFiles); // Use FileReader to read file content droppedFiles.forEach((file) => { const reader = new FileReader(); reader.onloadend = () => { console.log(reader.result); }; reader.onerror = () => { console.error("There was an issue reading the file."); }; reader.readAsDataURL(file); return reader; }); }; return ( <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px", width: "300px", border: "1px dotted", backgroundColor: isOver ? "lightgray" : "white", }} > Drag and drop some files here </div> ); }

In the handleDrop function, we first fetch the dropped files from event.dataTransfer.files, which is a FileList object. We convert this to an array for ease of handling.

We then read each file's content using the FileReader object. FileReader.readAsDataURL method is used to start reading the contents of the specified file. Once finished, the onloadend event is fired and the result attribute contains a data: URL representing the file's data. In case we need the file's text content, we can use FileReader.readAsText method instead.

Here, we're simply logging the file content to the console. In a real-world application, you might send this data to a server or use it in another part of your application. We're also storing the dropped files in our component's state for potential further use.

Conclusion

In this post, we've explored how to implement a drag-and-drop file upload feature in React using TypeScript and without the help of external libraries. We began with setting up the basic drag-and-drop events, then moved to reading the content of the files dropped into our drop zone.

Drag and drop is a versatile feature that can improve the user experience of your application, making file upload more interactive and intuitive. Although this tutorial provides a basic implementation, the potential is vast. You can extend this basic functionality to meet your application's needs, such as supporting different file types, integrating with a backend service to store the uploaded files, or providing real-time feedback to users during the upload process.

References and resources