Basic drag and drop implementation for nextjs (react)
Tags: javascript, beginners, nextjs, react
Published: 2023-08-15 20:00:02

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](https://github.com/react-dnd/react-dnd/issues/3488).

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 ](https://stackblitz.com/edit/vercel-next-js-7116vz?file=README.md)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>
  );
}
```

2. `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>
  );
}
```

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: [] },
  ]);
```

- 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);
  };
```

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');
      }
    }
  };
```

The full example is [here](https://stackblitz.com/edit/vercel-next-js-7116vz).

##Support touch devices
Just add a polyfill that enables HTML5 drag drop support on mobile (touch) devices. like [this ](https://github.com/Bernardo-Castilho/dragdroptouch)one.
```
      {(navigator.maxTouchPoints ||
        'ontouchstart' in document.documentElement) && (
        <Script src="/DragDropTouch.js" />
      )}
```