Drag and drop with React 18
A 3 mins tutorial to implement drag and drop with react 18. Code using state hooks and Typescript.
tl;dr;
Set the data to transfer on the transferObject
on the event onDragStart
, grabbing it from the element attributes. On the onDragEnter
(optional), simply set the state in order to visualize which element is currently dragged over, so that the element you can put a condition and render it differently. the onDrop
event get the data from the transferObject
and call your code.
A refresher of HTML drag attributes
onDragStart: This event is fired when a user begins to drag an element, allowing for custom actions to be taken at the start of a drag operation.
onDragStart
: Event triggered when an element starts to be dragged.onDragEnter
: Event triggered when a dragged element enters a valid drop target.onDragOver
: Event triggered continuously when a dragged element is over a valid drop target.onDrop
: Event triggered when a dragged element is dropped onto a valid drop target.onDragExit
: Event triggered when a dragged element leaves a valid drop target without dropping.
Example allowing a table to be reordered
In the example below I’m showing a simplified subset f real code I’m using to allow ordering of specifications for amazon products compared in a table. If you want to see it in action, download the chrome/edge extension, add few amazon products, then drag the specification up and down. I’ve inlined the functions to facilitate the reading.
This component displays a row, where the first column is draggable, and the other columns contains the actual values (don’t worry about this part to understand this example). Until not dragged, the current row is hightlighed in orange.
Imagine the table is as follows and your are dragging asin
over ean
.
| asin | 123 | 456 |
| isbn | 789 | 0xy |
| ean | abc | def |
Once you click on asin
row and hold the click, onDragStart
will be invoked, grabbing the spec name asin
from the column attribute(rendered as <tr data-spec-name=”asin”>
).
When you roll over a column (e.g. isbn), the onDragEnter
on that column will be called, and save the isbn
into the draggedSpecName
state. Note that since the state changed, the component will be re-rendered and the className of the column will change to have a bg-warning
class (orange twitter-bootstrap background).
Once you drop the element into ean
, the onDrop
on that row column will be called. Again, via the attribute we understand which row actually is (ean
), so we can call our custom code to change the order, and of course resetting the draggedSpecName
to avoid staying hightlighted.
Other minor things: onDragExit
needs to be set to reset the state property in case you interrupt the dragging, to avoid the current element to stay hightlighted. We don’t need onDragOver
as we already used onDragEnter
.
Here is the final code.
import * as React from "react";
export default function SpecRow(
{
specs,
specName,
draggedSpecName,
setDraggedSpecName
}: {
specs: string[],
specName: string,
draggedSpecName: string,
setDraggedSpecName: (s: string) => void
}) {
return <tr className="specs"
title="Click, hold and drag over to another row to change position"
draggable
data-spec-name={specName}
onDragStart={(e: any) => {
const specName = e.target.getAttribute('data-spec-name');
e.dataTransfer.setData("specName", specName);
}}
onDragOver={(e) => e.preventDefault()}
>
<td
className={`label ${specName == draggedSpecName ? 'bg-warning' : ''}`}
data-spec-name={specName}
onDragEnter={(e: any) => {
setDraggedSpecName(e.target.getAttribute('data-spec-name'));
}}
onDragOver={(e) => e.preventDefault()}
onDrop={(e: any) => {
const draggedInto = e.target.getAttribute('data-spec-name');
const draggedFrom = e.dataTransfer.getData("specName");
// do something here !
setDraggedSpecName('');
}}
onDragExit={(e) => setDraggedSpecName('')}
>{specName}</td>
{specs.map((specValue: string, key) => {
return (
<td key={key}>
{specValue}
</td>
);
})}
</tr>
}
Note that the parent component has to holds the state, as it’s across the various rows.
const [draggedSpecName, setDraggedSpecName] = useState('');
...
return <> ... <SpecRow
...
specName={specName}
...
draggedSpecName={draggedSpecName}
setDraggedSpecName={setDraggedSpecName}
/>
....
</>
Thanks for reading !
What to do next:
- Clap if useful
- Buy me a coffee
- Follow me for more
- Read my other articles
- Keep in touch on LinkedIn