Photo by Pierre Borthiry - Peiobty on Unsplash
Enhancing User Experience with a Custom File Uploader in React
Learn to build a Custom File Uploader with Drag-and-Drop Feature with React.js, Tailwind CSS and Typescript.
One key way to enhance user experience on the web is by simplifying how users interact with file uploads. Traditional file input elements can feel outdated and clunky, this is where custom file uploaders with drag-and-drop functionality come into play.
In this article, we will build a simple drag-and-drop file uploader using React and Tailwind CSS. By the end of this article, we'll have a fully functional drag-and-drop file uploader that looks great and offers a seamless user experience.
Prerequisite
To follow along with this article, you'll need the following:
Basic knowledge of React and Javascript
Basic knowledge of Tailwind CSS and utility classes
Node.js and npm installed on your machine
Basic knowledge of Typescript
Let's get started.
An image of what the custom drag-and-drop uploader will look like at the end of this article.
Setup Your Project
We assume you already have your React app created and Tailwind CSS config set up for this article. If you have yet to do that, you can find out how to do it using the resources below.
Now that you have created your React App and configured the Tailwind CSS config let's go ahead and create our custom uploader file.
In your src
folder create a new folder named CustomUploader
and then create a new file index.tsx
You can achieve this by typing this command into your terminal
For Windows OS:
mkdir src/CustomUploader && type nul > src/CustomUploader/index.tsx
For macOS/Linux:
mkdir -p src/CustomUploader && touch src/CustomUploader/index.tsx
Update the Content of the index.tsx
File
Now open the index.tsx
file you created and then write a simple functional component like below
const CustomFileUploader = ():JSX.Element => {
return (
<div>
hello world
</div>
)
}
export default CustomFileUploader;
Import the CustomFileUploader
component into your App.tsx
file and spin up your development server. Lunch your app in your preferred browser using http://localhost:5173/
and you will see just the text hello world
on your screen.
Style Up
Now let's dive deep and style the CustomFileUploader
component to look just like the above image.
Replace the div
in your CustomFileUploader.tsx
with the below code
<div className="flex flex-col gap-2">
<label htmlFor="fileUpload" className="text-lg text-black">
Upload Resume
</label>
<div className="py-12 px-4 bg-white border-2 border-dashed rounded-md cursor-pointer">
<p className="text-md text-gray-600 text-center">Drag & drop your files here or
<span className="underline font-semibold cursor-pointer">browse</span>
</p>
</div>
</div>
The Input Element
Next, we will add the HTML input element to our "border-dashed" div
. Add the code snippet below to the CustomFileUploader
component.
<input
id="fileUpload"
type="file"
accept=".pdf,.doc,.docx"
className="hidden"
/>
hidden
and an id
attribute attached to it.Here are a few reasons why;
Label Association: The
id
allows you to associate the input with a<label>
element. This makes the input more accessible by enabling users to click on the label to trigger the file input, rather than having to click directly on the input element, which is often styled ashidden
.Javascript/React interaction: The
id
also allows you to easily target and reference this specific input element in your JavaScript or React code, for instance, when accessing the DOM directly usingdocument.getElementById('fileInput')
, which we will get to in a moment.
Your CustomFileUploader
component should look like this by now.
const CustomFileUploader = ():JSX.Element => {
return (
<div className="flex flex-col gap-2">
<label htmlFor="fileUpload" className="text-lg text-black">
Upload Resume
</label>
<div className="py-12 px-4 bg-white border-2 border-dashed rounded-md cursor-pointer">
<input id="fileUpload" type="file" accept=".pdf,.doc,.docx" className="hidden"/>
<p className="text-md text-gray-600 text-center">Drag & drop your files here or
<span className="underline font-semibold cursor-pointer">browse</span>
</p>
</div>
</div>
)
}
export default CustomFileUploader;
Reload your browser window to see the changes we have made. You should see the dotted box, but we still can't upload a file.
Upload Functionality
Now to the fun part, let's add some functionality to our component.
First, we use the useState
hook to create and manage the state of the selected file, the initial value will be set to null
.
const [selectedFile, setSelectedFile] = useState<File | null>(null);
Next, we create our handleFileChange
function
This function handles the file input changes. It accepts a parameter event
, an object of the type React.ChangeEvent<HTMLInputElement>
representing a change event triggered by a file input element. We then extract the selected file from the event target and check if the files
property is true (i.e., not null or undefined). If files
is true, we then call the setSelectedFile()
function passing the first file in the files
array as an argument.
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
setSelectedFile(files[0]);
}
};
We then add an onChange
event handler to our input element and pass the handleFileChange
function to it.
//updated input element
<input
id="fileInput"
type="file"
accept=".pdf,.doc,.docx"
className="hidden"
onChange={handleFileChange} // include this line
/>
We now have a good-looking File uploader, but it still can't handle our file upload. To fix this, we introduce a function triggerFileUpload
.
const triggerFileUpload = () => {
document.getElementById("fileInput")?.click()
}
Let's break the triggerFileUpload
function down to understand its components.
document.getElementById("fileInput")
extracts an HTML element with the id "fileInput" from the document.The
?.
called the optional chaining operator. It lets you access/read a property's value located deep within a chain of related objects without verifying each reference's validity. Confused ???
Here is a simpler explanation for you my friend.
click()
is a method that simulates a click event on the retrieved element. When called on a file input element, it opens the file selection dialog, allowing the user to select a file.
And then we call the triggerFileUpload
function on the span
tag where we have the word browse
so when a user clicks on browse
, their default file explorer opens up.
<span
className="underline font-semibold cursor-pointer"
onMouseDown={triggerFileUpload}
>
browse
</span>
// you can choose to use onClick() if that is what you prefer
Our CustomFileUploader
component can now select and upload a file. Let's go ahead and wrap it up by introducing the drag-and-drop feature/functionality.
Drag-and-Drop
For that, we use a state variable to track whether an element(in our case file) is being dragged or not.
const [isDragging, setIsDragging] = React.useState<boolean>(false);
Then we have the below functions;
const handleDragOver = (event: { preventDefault: () => void; }) => {
event.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleDrop = (event: { dataTransfer: { files: any } }) => {
const file = event.dataTransfer.files;
if (file) {
setSelectedFile(file[0]);
}
setIsDragging(false);
};
The
handleDragOver
andhandleDragLeave
functions call thesetIsDragging
function from our useState hook and sets theisDragging
state variable totrue
orfalse
respectively.The
handleDrop
function does the same thing as ourhandleFileChange
.Only this time we extract the dragged/selected file eventdataTransfer
object and not the eventtarget
of the event object.
We then call these functions on our CustomFileUploader
parent div
. I.e the div
with dotted borders like this.
<div
className="py-12 px-4 bg-white border-2 border-dashed rounded-md cursor-pointer"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
.......other code
</div>
You will notice that we introduced some event handlers to our parent div
to help us achieve the drag-and-drop functionality. Let's go over these event handlers quickly.
OnDragOver: This event handler is called when a user drags an element over the parent
div
element. thehandleDragOver
function is executed when this event occurs.OnDragLeave: When a user drags an element out of the parent
div
element. ThehandleDragLeave
function is fired when this event happens.OnDrop: This event handler is called when a user drops an element into the parent
div
and in turn, thehandleDrop
function is executed when this event occurs.
If you follow this article to this point, Congratulations you now have a functional custom file uploader with drag-and-drop functionality. Your final code should be like what we have below.
const CustomFileUploader = (): JSX.Element {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isDragging, setIsDragging] = useState<boolean>(false);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
setSelectedFile(files[0]);
}
};
const triggerFileUpload = () => {
document.getElementById("fileInput")?.click();
};
const handleDragOver = (event: { preventDefault: () => void; }) => {
event.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleDrop = (event: { dataTransfer: { files: any } }) => {
const file = event.dataTransfer.files;
if (file) {
setSelectedFile(file[0]);
}
setIsDragging(false);
};
return (
<div className="flex justify-center items-center h-screen">
<div className="flex flex-col gap-2 w-1/2">
<label htmlFor="fileupload" className="text-lg text-black">
Upload Resume
</label>
<div
className="py-12 px-4 bg-white border-2 border-dashed rounded-md cursor-pointer"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
id="fileInput"
type="file"
accept=".pdf,.doc,.docx"
className="hidden"
onChange={handleFileChange}
/>
<p className="text-md text-gray-600 text-center">
Drag & drop your files here or{" "}
<span
className="underline font-semibold cursor-pointer"
onMouseDown={triggerFileUpload}
>
browse
</span>
</p>
</div>
{selectedFile?.name}
</div>
</div>
);
}
export default CustomFileUploader;
Conclusion
In this article, we've taken a comprehensive journey through building a custom drag-and-drop file uploader using React and Tailwind CSS. From setting up the basic structure to implementing drag-and-drop functionality and file selection, we've covered all the essential steps to create a functional and visually appealing file uploader.
You now know how to create a seamless file uploader with a great user experience, using React's component-based architecture and Tailwind CSS's utility-first approach.
Whether you're building a simple blog or a complex enterprise application, a custom file uploader can elevate your user interface and improve overall user experience. I hope this article has provided you with the knowledge and inspiration to take your file-uploading game to the next level.
Happy coding!!!