Hello Guys, In this post we will walk you through creating a drag-and-drop file upload component in Next.js. This component allows users to upload files by dragging them onto a designated area or using a traditional file selection dialog and previewing the file. We are focusing on images here but it can be expanded to other file types as well.
Begin by setting up your Next.js project.
npx create-next-app@latest
In src/components, let’s create the FileUpload component file_upload.tsx
,
Imports and Setup:
useState
from React. This hook allows us to manage the component's state.file
: This state variable stores the URL of the uploaded file (initially empty).fileEnter
: This boolean state tracks whether a file is currently being dragged over the dropzone (initially false) using this we update the border styles of our drag/drop container for visualization.Code:
"use client"
import { useState } from "react";
export const FileUpload = () => {
const [file, setFile] = useState<string>();
const [fileEnter, setFileEnter] = useState(false);
// Component logic goes here
}
Sure, let’s break down the code step by step:
For the container, we have the following events to handle the drag-and-drop events for the file.
onDragOver
:e.preventDefault()
prevents the default behavior of the browser from occurring, which is to open the dropped file in the browser window.setFileEnter(true)
updates the state variable fileEnter
to true
, indicating that a file is being dragged over the drop area.2. onDragLeave
and onDragEnd
:
onDragLeave
is triggered when a dragged item leaves the drop area, while onDragEnd
is triggered when the drag operation is canceled (e.g., by pressing the Escape key).fileEnter
to false
, indicating that no file is being dragged over the drop area anymore.3. onDrop
:
e.preventDefault()
prevents the default behavior of the browser, which is to open the dropped file in the browser window.setFileEnter(false)
sets fileEnter
to false
to indicate that no file is being dragged over the drop area.e.dataTransfer.items
is available. If it is, it means that the drag operation involves files.e.dataTransfer.items
exists, it iterates through each item using forEach
. If the item’s kind
is "file"
, it retrieves the file object using getAsFile()
. Also, you can do some other processing on the file based on your needs for eg- you can capture the extension of the file and throw errors if you accept only specific file types.URL.createObjectURL()
which is used for creating a temporary, unique URL (often called a blob URL) that points to a piece of data stored in the browser's memory. This data isn't saved on the user's device or your server; it's just accessible within the current browser session. and updates the file
state variable with this URL using setFile(blobUrl)
.e.dataTransfer.items
is not available, it means that the drag operation involves files directly (not as items).4. JSX:
fileEnter
.label
and input
elements provide an alternative way for users to upload files by clicking and selecting them manually. The label
serves as a visual indicator and instruction for users to click and upload files.input
element with type="file"
is hidden but is still functional, allowing users to click and select files from their device. Also, we will generate the blob URL as implemented for drop functionality.Code:
...
<div className="container px-4 max-w-5xl mx-auto">
{!file ? (
<div
onDragOver={(e) => {
e.preventDefault();
setFileEnter(true);
}}
onDragLeave={(e) => {
setFileEnter(false);
}}
onDragEnd={(e) => {
e.preventDefault();
setFileEnter(false);
}}
onDrop={(e) => {
e.preventDefault();
setFileEnter(false);
if (e.dataTransfer.items) {
[...e.dataTransfer.items].forEach((item, i) => {
if (item.kind === "file") {
const file = item.getAsFile();
if (file) {
let blobUrl = URL.createObjectURL(file);
setFile(blobUrl);
}
console.log(`items file[${i}].name = ${file?.name}`);
}
});
} else {
[...e.dataTransfer.files].forEach((file, i) => {
console.log(`… file[${i}].name = ${file.name}`);
});
}
}}
className={`${
fileEnter ? "border-4" : "border-2"
} mx-auto bg-white flex flex-col w-full max-w-xs h-72 border-dashed items-center justify-center`}
>
<label
htmlFor="file"
className="h-full flex flex-col justify-center text-center"
>
Click to upload or drag and drop
</label>
<input
id="file"
type="file"
className="hidden"
onChange={(e) => {
console.log(e.target.files);
let files = e.target.files;
if (files && files[0]) {
let blobUrl = URL.createObjectURL(files[0]);
setFile(blobUrl);
}
}}
/>
</div>
...
Next, we have to show the preview of the uploaded file. If a file is uploaded (file
is not empty), container displays the uploaded file using an img
element and the src
attribute points to the file
state (blob URL). A “Reset” button allows users to clear the uploaded file by setting file
it back to an empty string. Finally, we check if the file does not exist and then show the drag/drop container input else show the preview component.
Final Code:
"use client";
import { useState } from "react";
export const FileUpload = () => {
const [file, setFile] = useState<string>();
const [fileEnter, setFileEnter] = useState(false);
return (
<div className="container px-4 max-w-5xl mx-auto">
{!file ? (
<div
onDragOver={(e) => {
e.preventDefault();
setFileEnter(true);
}}
onDragLeave={(e) => {
setFileEnter(false);
}}
onDragEnd={(e) => {
e.preventDefault();
setFileEnter(false);
}}
onDrop={(e) => {
e.preventDefault();
setFileEnter(false);
if (e.dataTransfer.items) {
[...e.dataTransfer.items].forEach((item, i) => {
if (item.kind === "file") {
const file = item.getAsFile();
if (file) {
let blobUrl = URL.createObjectURL(file);
setFile(blobUrl);
}
console.log(`items file[${i}].name = ${file?.name}`);
}
});
} else {
[...e.dataTransfer.files].forEach((file, i) => {
console.log(`… file[${i}].name = ${file.name}`);
});
}
}}
className={`${
fileEnter ? "border-4" : "border-2"
} mx-auto bg-white flex flex-col w-full max-w-xs h-72 border-dashed items-center justify-center`}
>
<label
htmlFor="file"
className="h-full flex flex-col justify-center text-center"
>
Click to upload or drag and drop
</label>
<input
id="file"
type="file"
className="hidden"
onChange={(e) => {
console.log(e.target.files);
let files = e.target.files;
if (files && files[0]) {
let blobUrl = URL.createObjectURL(files[0]);
setFile(blobUrl);
}
}}
/>
</div>
) : (
<div className="flex flex-col items-center">
<object
className="rounded-md w-full max-w-xs h-72"
data={file}
type="image/png" //need to be updated based on type of file
/>
<button
onClick={() => setFile("")}
className="px-4 mt-10 uppercase py-2 tracking-widest outline-none bg-red-600 text-white rounded"
>
Reset
</button>
</div>
)}
</div>
);
};
Finally, you can add this component to the page you want and run the Next JS app using npm run dev
Visit http://localhost:3000
in your browser to see it in action.
Congratulations! You’ve successfully built a drag/drop file upload component with a preview functionality in a Next.js application. Feel free to expand for multiple file types, and integrate these techniques into your projects. I would appreciate it if you could take a moment to share your thoughts in the comments section below. Let me know what new information or insights you gained from this post. If you find any bugs/errors please post it and comment. Thank you for reading!.