Back to Blog

Dropzone: File Drag & Drop | NextJS 14 Tutorial

Oct 23, 2024

Picture of a battle in my game Random Fate

Dropzone used to drag & drop files for your website

How does it work?

This dropzone uses native HTML ondragenter, ondrag, ondrop event listeners to provide drag & drop functionality with zero dependencies.

It also uses a file callback function to allow it to have as much flexibility as possible. So you can use the files in any way you want.

First: Make the client side wrapper

In the example I show that this dropzone can be used to easily add/remove files to a list. We'll add that feature later. Make sure you import this into a navigatable page from NextJS

1"use client"
2
3export default function ClientSideWrapper() {
4    return(
5         <div>
6            <Dropzone />
7        </div>
8    )
9}
10

Now setup the dropzone component

I'm using tailwind to style it, but you can use whatever styling method you want.

1"use client"
2import React from "react";
3
4type DropzoneProps = {
5    acceptedFiletypes: string;
6    fileCallback(files: File[]): void;
7    multiple?: boolean;
8    name?: string;
9}
10
11export default function Dropzone() {
12    return(
13        <div
14            className='flex flex-col justify-center items-center border-dashed border-2 border-[#ccc]'
15        >
16            <input
17                id={`fileSelect${name}`}
18                type="file"
19                className='border-0 clip-[rect(0,0,0,0)] h-[1px] overflow-hidden p-0 absolute whitespace-nowrap w-[1px] hidden'
20            />
21            <label htmlFor={`fileSelect${name}`} className="mt-[1rem] bg-blue-800 rounded !text-inherit cursor-pointer 
22                inline-block px-1 py-3 text-center select-none hover:text-[#fff] focus:text-[#fff] focus:outlline-dotted focus:outline-[5px] focus:outline-white">
23                Select {name}
24            </label>
25
26            <h3 className='my-[1em] text-white'>
27                or drag &amp; drop your {name} here
28            </h3>
29        </div>
30    )
31}

What are these props?

Now add the event listeners and ids

This is only the Dropzone's return value

1<div
2    className='flex flex-col justify-center items-center border-dashed border-2 border-[#ccc]'
3    onDragEnter={(e) => handleDragEnter(e)}
4    onDragOver={(e) => handleDragOver(e)}
5    onDragLeave={(e) => handleDragLeave(e)}
6    onDrop={(e) => handleDrop(e)}
7>
8    <input
9        id={`fileSelect${name}`}
10        type="file"
11        accept={acceptedFileTypes}
12        className='border-0 clip-[rect(0,0,0,0)] h-[1px] overflow-hidden p-0 absolute whitespace-nowrap w-[1px] hidden'
13        onChange={(e) => handleFileSelect(e)}
14        multiple={multiple}
15    />
16    <label htmlFor={`fileSelect${name}`} className="mt-[1rem] bg-blue-800 rounded !text-inherit cursor-pointer inline-block px-1 py-3 text-center select-none
17        hover:text-[#fff] focus:text-[#fff] focus:outlline-dotted focus:outline-[5px] focus:outline-white">Select {name}</label>
18
19    <h3 className='my-[1em] text-white'>
20        or drag &amp; drop your {name} here
21    </h3>
22</div>

Start Drag & Drop

ondragenter, ondragover, and ondragleave are just to stop refreshing (preventDefault) and highlighting (stopPropagation)

We also make sure the drop effect is set to copy in the ondragenter function

1// onDragEnter stops refreshing & highlighting & makes sure the dropEffect is "copy"
2const handleDragEnter = (e: MouseEvent<HTMLDivElement>) => {
3    e.preventDefault();
4    e.stopPropagation();
5
6    e.dataTransfer.dropEffect = "copy";
7};
8
9// onDragOver stops refreshing & highlighting
10const handleDragOver = (e: MouseEvent<HTMLDivElement>) => {
11    e.preventDefault();
12    e.stopPropagation();
13};
14
15// onDragLeave stops refreshing & highlighting
16const handleDragLeave = (e: MouseEvent<HTMLDivElement>) => {
17    e.preventDefault();
18    e.stopPropagation();
19};

On Drop & File Select

1// onDrop adds files to fileList
2const handleDrop = async (e: MouseEvent<HTMLDivElement>) => {
3    e.preventDefault();
4    e.stopPropagation();
5    // Additional info on these next few lines in the next paragraph
6    let isDropzoneFile: string = e.dataTransfer.getData("isDropzoneFile")
7    if (isDropzoneFile.includes("false")) {
8        return
9    }
10
11    // get files from event on the dataTransfer object as an array
12    let files = [...e.dataTransfer.files];
13    // ensure a file or files are dropped
14    if (files && files.length > 0) {
15        fileCallback(files);
16    }
17};
18
19// handle file selection via input element
20const handleFileSelect = async (e: ChangeEventHandler<HTMLInputElement>) => {
21    // get files from event on the input element as an array
22    let files = [...e.target.files];
23    // ensure a file or files are dropped
24    if (files && files.length > 0) {
25        fileCallback(files);
26    }
27};

Notice: The lines below allows you to not drop certain elements. So if you have a drag & sort component on the same page. These lines will help stop the sorting elements from getting dropped into the dropzone. Just set the dataTransfer variable `isDropzoneFile` to false on the sorting elements.

1let isDropzoneFile: string = e.dataTransfer.getData("isDropzoneFile")
2if (isDropzoneFile.includes("false")) {
3    return
4}

Update the Client Side Wrapper

1"use client"
2export default function ClientSideWrapper() {
3    const fileFunction = (files: File[]): void => {
4        // Do whatever you want with the dropped files here
5    }
6
7    return(
8         <div>
9            <DropZone name="Media" acceptedFileTypes="image/png,image/jpeg,video/*" fileCallback={fileFunction} multiple/>
10        </div>
11    )
12}

Follow Me