DEV Community

Cover image for Basic drag and drop implementation for nextjs (react)
medmor
medmor

Posted on

Basic drag and drop implementation for nextjs (react)

There are some very good packages that implement drag and drop for react, but they are very complicated for me. They use state management libraries like redux, and always needed lot of setups to get things right.

If you just want to change a cursor style, you need to dive in the package codebase and try to understand how it works, and you may not succeed, like here.

Why all that? aren't drag and drop just html events like onclick or onchange...

Yes the are, and for simple use cases, the implementation is simpler than using a third-party solution.

In this article I will show a simple example for drag and drop functionalities.

Here is the example on StackBlitz.

Three events needed

To implement this simple example, we need to listen to dragstart event on the draggable element, and listen to dragover and drop events on the container or droppable element.
For that we can create two components:

  1. Draggable component: Here we can use a div that has draggable attribute and listen to dragstart event. The implementation could be something like this:
import { DragEvent } from 'react';

interface DraggableProps {
  children?: React.ReactNode;
  className?: string;
  onDragStart?: (e: DragEvent<HTMLElement>) => void;
}
export default function Draggable(props: DraggableProps) {
  const onDragStart = (e: DragEvent<HTMLElement>) => {
    if (props.onDragStart) {
      props.onDragStart(e);
    }
  };
  return (
    <div className={props.className} onDragStart={onDragStart} draggable>
      {props.children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. Droppable component: Here also we can use a div and listen to dragover and drop events. The implementation could be something like this:
import { DragEvent } from 'react';

interface DroppableProps {
  children?: React.ReactNode;
  className?: string;
  onDrop?: (e: DragEvent<HTMLElement>) => void;
  onDragOver?: (e: DragEvent<HTMLElement>) => void;
}
export default function Droppable(props: DroppableProps) {
  const onDrop = (e: DragEvent<HTMLElement>) => {
    e.preventDefault();
    if (props.onDrop) {
      props.onDrop(e);
    }
  };
  const onDragOver = (e: DragEvent<HTMLElement>) => {
    e.preventDefault();
    if (props.onDragOver) {
      props.onDragOver(e);
    }
  };
  return (
    <div className={props.className} onDrop={onDrop} onDragOver={onDragOver}>
      {props.children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note that we can pass callbacks to those components. Those callback will be used by the parent components to manage their state based on those events.

Manage the state in the parent component

In this example, the parent component state consiste of:

  • Tho containers: one for the even numbers and the other for the odd ones.
 const [containers, setContainers] = useState<
    { name: string; numbers: number[] }[]
  >([
    { name: 'odd', numbers: [] },
    { name: 'even', numbers: [] },
  ]);
Enter fullscreen mode Exit fullscreen mode
  • An array of items: just array of integers.
    const [items, setItems] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9]);

  • The current dragged item:
    const [currentItem, setCurrentItem] = useState<number>();

To manage the state we use an onDragStart callback to set the currentItem.

  const onDragStart = (item: number) => {
    setCurrentItem(item);
  };
Enter fullscreen mode Exit fullscreen mode

And an onDrop callback to accepte the number or refuse it on each container.

  const onDrop = (index: number) => {
    if (containers[index].name == 'even') {
      if (currentItem % 2 == 0) {
        console.log('accept even number');
        setItems((items) => items.filter((item) => item != currentItem));
        containers[index].numbers.push(currentItem);
        setContainers((containers) => containers);
      } else {
        console.log('refuse odd number');
      }
    } else {
      if (currentItem % 2 != 0) {
        console.log('accept odd number');
        setItems((items) => items.filter((item) => item != currentItem));
        containers[index].numbers.push(currentItem);
        setContainers((containers) => containers);
      } else {
        console.log('refuse even number');
      }
    }
  };
Enter fullscreen mode Exit fullscreen mode

The full example is here.

Support touch devices

Just add a polyfill that enables HTML5 drag drop support on mobile (touch) devices. like this one.

      {(navigator.maxTouchPoints ||
        'ontouchstart' in document.documentElement) && (
        <Script src="/DragDropTouch.js" />
      )}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)