Building a Drag-and-Drop File Uploader with Next.js

thumbnail

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.

Step 1: Setting Up the Project

Begin by setting up your Next.js project.

npx create-next-app@latest

Step 2: Creating the Drag/Drop File Upload Component

In src/components, let’s create the FileUpload component file_upload.tsx,

Imports and Setup:

  • We start by importing 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:

Drag/Drop Container & Input:

For the container, we have the following events to handle the drag-and-drop events for the file.

  1. onDragOver:
  • This event is triggered when an item is being dragged over the drop area.
  • 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).
  • Both events set fileEnter to false, indicating that no file is being dragged over the drop area anymore.

3. onDrop:

  • This event is triggered when a dragged item is dropped onto the drop area.
  • 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.
  • The conditional statement checks if e.dataTransfer.items is available. If it is, it means that the drag operation involves files.
  • If 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.
  • Next, we generate a blob URL for the file using 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).
  • If e.dataTransfer.items is not available, it means that the drag operation involves files directly (not as items).

4. JSX:

  • Dynamically adjust the border of the drag/drop container div based on the value of 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.
  • The 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 imgelement 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 devVisit 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!.