DEV Community

Cover image for How to create a Custom Drag & Drop Hook in React!πŸ”₯
Shaan Alam
Shaan Alam

Posted on

How to create a Custom Drag & Drop Hook in React!πŸ”₯

Hello guys!!
Recently, while working on a project, I came accross a situation where I needed to implement Drag and drop feature for uploading files to the server. But, the problem was that, the (component) file in which I was working was already large, so I wanted to somehow separate the drag and drop logic from my component logic, and I ended up creating a useDragAndDrop() hook.

In this article, I want to share exactly, how I created a custom hook for drag and drop feature.

So, let's build it!! πŸ”₯

Let me show you the end result of what we will be building.
ezgif-4-7bd93a6caeeb.gif

Initialize a React Project.

npx create-react-app drag-and-drop-react --template typescript

After the project is initialized, let's create a form in App.js.

import "./App.css";

function App() {
  return (
    <div className="container">
      <form>
        <label htmlFor="file">
          <h1>Select Or drop a file</h1>
        </label>
        <input type="file" name="file" id="file" />
      </form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In the App.css, make the following changes to make it look beautiful, or you can do your own custom styling!


@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  background: #0267c1;
  height: 100vh;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.container {
  background: #fff;
  padding: 3em 5em;
  border-radius: 0.5em;
  box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.02),
    0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035),
    0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05),
    0 100px 80px rgba(0, 0, 0, 0.07);
}

input {
  display: none;
}

label {
  width: 400px;
  border: 3px #efa00b dashed;
  padding: 3em 1.78em;
  display: inline-block;
  cursor: pointer;
  font-family: "Noto Sans JP", sans-serif;
}

label h1 {
  font-size: 130%;
  text-align: center;
  color: #efa00b;
}

.file-drop-error {
  width: 100%;
  text-align: center;
  color: red;
  display: inline-block;
  margin-bottom: 1em;
}

.file-properties {
  margin-top: 1em;
}

.file-properties ul {
  list-style: none;
  text-align: center;
}

Enter fullscreen mode Exit fullscreen mode

Now, let's create the hook

Create a folder called hooks and inside it create a file "useDragAndDrop.ts". In this file, we will create the necessary state and methods for the drag and drop feature.

First we will create two states dragOver and fileDropError. DragOver will determine if the file is being dragged over our label element, and fileDropError will contain the error message if there is any error after dropping the file. In this case, we want an image, so the fileDropError will likely store error messages related to file not being of the type image.

  const [dragOver, setDragOver] = useState(false);
  const [fileDropError, setFileDropError] = useState(")

Enter fullscreen mode Exit fullscreen mode

Next, we will create two function onDragOver() and onDragLeave(), this will the toggle the state of dragOver, and then will return all the states and method.

 const onDragOver = (e: React.SyntheticEvent) => {
    e.preventDefault();
    setDragOver(true);
  };

  const onDragLeave = () => setDragOver(false);
Enter fullscreen mode Exit fullscreen mode

The final code will look like this.

import { useState } from "react";

export default function useDragAndDrop() {
  const [dragOver, setDragOver] = useState(false);
  const [fileDropError, setFileDropError] = useState("")

  const onDragOver = (e: React.SyntheticEvent) => {
    e.preventDefault();
    setDragOver(true);
  };

  const onDragLeave = () => setDragOver(false);

  return {
    dragOver,
    setDragOver,
    onDragOver,
    onDragLeave,
    fileDropError,
    setFileDropError,
  };
}
Enter fullscreen mode Exit fullscreen mode

And that's it for this file. Now, let's come back to App.js and implement our hook.

Import useDragAndDrop from the hooks folder. and then destructure all the values from our hook. We will also need a file state to store the file.

import React, { useState } from "react";
import "./App.css";
import useDragAndDrop from "./hooks/useDragAndDrop";

function App() {
  const [file, setFile] = useState<File>();

  const {
    dragOver,
    setDragOver,
    onDragOver,
    onDragLeave,
    fileDropError,
    setFileDropError,
  } = useDragAndDrop();

 return (
    <div className="container">
      <form>
        <label htmlFor="file">
          <h1>Select Or drop a file</h1>
        </label>
        <input type="file" name="file" id="file" />
      </form>
    </div>
}
Enter fullscreen mode Exit fullscreen mode

Now let's specify the onDragOver and onDragLeave methods on our label like this. Please note that the onDrop function here is not related to our hook, but will be called when the file is dropped. We will create this function in the App.js itself.

We also add some conditional styling and conditional rendering. This will just change the border styling and the text inside the label once the file is being dragged over the label. (Look in the picture) below

         <label
          htmlFor="file"
          onDragOver={onDragOver}
          onDragLeave={onDragLeave}
          onDrop={onDrop}
          style={{ border: `${dragOver ? "3px dashed yellowgreen" : ""}` }}
        >
          {file && <h1>{file.name}</h1>}
          {!file && (
            <h1 style={{ color: `${dragOver ? " yellowgreen" : ""}` }}>
              {!dragOver ? "Select Or Drop your File here..." : "Drop here..."}
            </h1>
          )}
        </label>
Enter fullscreen mode Exit fullscreen mode

Screenshot from 2021-08-19 12-54-10.png

Now, we will create the onDrop() and fileSelect() function for setting the file state after the file is dropped or selected.

   const onDrop = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();

    setDragOver(false);

    const selectedFile = e?.dataTransfer?.files[0];

    if (selectedFile.type.split("/")[0] !== "image") {
      return setFileDropError("Please provide an image file to upload!");
    }

    setFile(selectedFile);
  };

  const fileSelect = (e: any) => {
    let selectedFile = e?.dataTransfer?.files[0];

    if (selectedFile.type.split("/")[0] !== "image") {
      return setFileDropError("Please provide an image file to upload!");
    }

    setFileDropError("");
  };
Enter fullscreen mode Exit fullscreen mode

Mention the onChange handler on the input element.
<input type="file" name="file" id="file" onChange={fileSelect} />

Also, we need to conditionally render any error we have.

 {fileDropError && (
    <span className="file-drop-error">{fileDropError}</span>
 )}
Enter fullscreen mode Exit fullscreen mode

Final App.js looks like this.

import React, { useState } from "react";
import "./App.css";
import useDragAndDrop from "./hooks/useDragAndDrop";

function App() {
  const {
    dragOver,
    setDragOver,
    onDragOver,
    onDragLeave,
    fileDropError,
    setFileDropError,
  } = useDragAndDrop();

  const [file, setFile] = useState<File>();

  const onDrop = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();

    setDragOver(false);

    const selectedFile = e?.dataTransfer?.files[0];

    if (selectedFile.type.split("/")[0] !== "image") {
      return setFileDropError("Please provide an image file to upload!");
    }

    setFile(selectedFile);
  };

  const fileSelect = (e: any) => {
    let selectedFile = e?.dataTransfer?.files[0];

    if (selectedFile.type.split("/")[0] !== "image") {
      return setFileDropError("Please provide an image file to upload!");
    }

    setFileDropError("");
  };

  return (
    <div className="container">
      <form>
        {fileDropError && (
          <span className="file-drop-error">{fileDropError}</span>
        )}
        <label
          htmlFor="file"
          onDragOver={onDragOver}
          onDragLeave={onDragLeave}
          onDrop={onDrop}
          style={{ border: `${dragOver ? "3px dashed yellowgreen" : ""}` }}
        >
          {file && <h1>{file.name}</h1>}
          {!file && (
            <h1 style={{ color: `${dragOver ? " yellowgreen" : ""}` }}>
              {!dragOver ? "Select Or Drop your File here..." : "Drop here..."}
            </h1>
          )}
        </label>
        <input type="file" name="file" id="file" onChange={fileSelect} />
      </form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

And That's it we have successfully created custom Drag & Drop hook!

I hope you liked the article and gained some knowledge as well. All the codes used are there in the github repository mentioned below.
Github Repo - https://github.com/shaan71845/react-custom-drag-and-drop-hook

Discussion (2)

Collapse
maxprogramming profile image
Max Programming

This is great πŸ”₯πŸ”₯!
One of the best examples for custom hooks in React!

Collapse
shaan_alam profile image
Shaan Alam Author

Thanks @maxprogramming !