Make Drag & Drop + DropZone with interact.js + reactjs
Tags: typescript, react, webdev, tutorial
Published: 2022-11-01 03:30:41


## What is interact.js
https://interactjs.io/
> JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).


{% github taye/interact.js %}


Recently I've worked on Electron + reactjs for h/w(hardware). Then I needed to implement gestures such as long press, swipe, scroll up/down, and drag & drop.

Actually, I thought if I used [hammerjs](https://hammerjs.github.io/), it would be easy, but actually I gave up using that since it seemed that hammerjs's development wasn't active any more unfortunately.

The last tag was 2016, for personal development that would be fine but the project isn't a personal project so I gave up using that. Then I was doing some research on [Openbase](https://openbase.com).


I checked a few libraries and created a compare list to get feedback from team members. Then we decided to use interact.js because the number of starts is good and it supports gestures the design team requested. In addition, its document is well-organized and we can use it with typescript easily.

docs
https://interactjs.io/docs/

In this article, I will introduce dropzone ui with interact.js + react hooks.

<img src="https://media.giphy.com/media/hop9t4dJcMFLfrNyEB/giphy.gif" />


`useDrag.ts`
https://interactjs.io/docs/draggable/

If you want to use vanillajs, you can use the sample code directly.

```ts
import React from "react";
import interact from "interactjs";

type Partial<T> = {
  [P in keyof T]?: T[P];
};

const initialPosition = {
  width: 100,
  height: 100,
  x: 0,
  y: 225
};

export const useDraggable = (
  position: Partial<typeof initialPosition> = initialPosition
) => {
  const [elementPosition, setElementPosition] = React.useState<
    typeof initialPosition
  >({
    ...initialPosition,
    ...position
  });

  const [isEnabled, setIsEnabled] = React.useState<boolean>(true);

  const interactiveRef = React.useRef(null);

  let { x, y, width, height } = elementPosition;

  const enable = () => {
    interact((interactiveRef.current as unknown) as HTMLElement)
      .draggable({
        modifiers: [],
        inertia: false
      })
      .on("dragmove", (event) => {
        x += event.dx;
        y += event.dy;

        setElementPosition({
          width,
          height,
          x,
          y
        });
      });
  };

  const disable = () => {
    interact((interactiveRef.current as unknown) as HTMLElement).unset();
  };

  React.useEffect(() => {
    if (isEnabled) {
      enable();
    } else {
      disable();
    }
    return disable;
  }, [isEnabled]);

  return {
    ref: interactiveRef,
    style: {
      transform: `translate3D(${elementPosition.x}px, ${elementPosition.y}px, 0)`,
      width: `${elementPosition.width}px`,
      height: `${elementPosition.height}px`,
      position: "absolute" as React.CSSProperties["position"],
      touchAction: "none"
    },
    position: elementPosition,
    isEnabled,
    enable: () => setIsEnabled(true),
    disable: () => setIsEnabled(false)
  };
};
```
 
`App.tsx`
https://interactjs.io/docs/dropzone/


```ts
import { useRef } from "react";
import interact from "interactjs";
import "./styles.css";
import { useDraggable } from "./hooks/useDraggable";

export default function App() {
  const targetRef = useRef(null);
  const draggable = useDraggable();

  // dropzone
  if (targetRef?.current) {
    interact((targetRef.current as unknown) as HTMLElement)
      .dropzone({
        accept: ".test",
        overlap: 0.75
      })

      .on("dropactivate", (event: Interact.InteractEvent) => {
        event.target.classList.add("drop-active");
      })

      .on("dragenter", (event: Interact.InteractEvent) => {
        console.log("dragenter");
        const draggableElement = event.relatedTarget;
        const dropzoneElement = event.target;
        dropzoneElement.classList.add("drop-target");
        draggableElement?.classList.add("can-drop");
        if (draggableElement) {
          draggableElement.style.color = "#fff";
          draggableElement.textContent = "release me";
        }
      })

      .on("dragleave", (event: Interact.InteractEvent) => {
        const draggableElement = event.relatedTarget;
        const dropzoneElement = event.target;
        dropzoneElement.classList.remove("drop-target");
        draggableElement?.classList.remove("can-drop");
        if (draggableElement) {
          draggableElement.textContent = "dragging me";
        }
      })

      .on("drop", (event: Interact.InteractEvent) => {
        const draggableElement = event.relatedTarget;
        if (draggableElement) {
          draggableElement.style.color = "#fff";
          draggableElement.textContent = "hello world";
        }
      })

      .on("dropdeactivate", (event: Interact.InteractEvent) => {
        event.target.classList.remove("drop-active");
        event.target.classList.remove("drop-target");
      });
  }

  return (
    <div className="App">
      <h1>interactjs samples</h1>
      <div className="test" ref={draggable.ref} style={draggable.style}>
        <p>drag me</p>
        <p>
          {`x: ${draggable.position.x.toFixed(0)}`}
          {`y: ${draggable.position.y.toFixed(0)}`}
        </p>
      </div>
      <div className="dropzone" ref={targetRef}>
        dropzone
      </div>
    </div>
  );
}
```