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:
-
Draggable
component: Here we can use a div that hasdraggable
attribute and listen todragstart
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>
);
}
-
Droppable
component: Here also we can use a div and listen todragover
anddrop
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.
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" />
)}
Top comments (0)