When I decided I wanted to learn how to create components that I could drag and drop, I assumed it was going to be some totally complicated rigamarole that would almost certainly require a third-party library to implement. I was pleasantly surprised to find that the whole “drag and drop” concept has its own HTML API, and it’s really not hard to use at all! So how does it work and how can you use it in your own application?
HTML Drag and Drop API documentation - if you’re the type that just wants to dig into the docs on their own, you can hit ‘em up here!
What do you want to drag? Make it draggable!
In our case, we have a list of drag queens and we want to make a new list by dragging and dropping our favorites. By default, HTML elements are, as you might expect, not draggable. It would be kind of strange if you could just click and drag any old thing on the internet into any other old place on the internet, right?
What do you want to drag? In our case, we want to be able to drag the list items in this unordered list:
const QueensList = () => {
const queens = [
'Divine',
'Lady Bunny',
'Sasha Velour',
'Vaginal Creme Davis',
'The Fabulous Wonder Twins'
]
return (
<ul>
{
queens.map((queenName) => (
<Queen name={queenName} />
))
}
</ul>
)
}
const Queen = ({ name }) => (
<li>
{name}
</li>
)
In order to make these items draggable, we have to mark them as such:
const Queen = ({ name }) => (
<li draggable='true'>{name}</li>
)
If we want the text of the list item to make it to the other element, we also have to add the item to the drag event’s drag data. We can do this with the setData()
method on the drag event’s dataTransfer
property, and we do this in the onDragStart
method (which, as you may have guessed, is what gets triggered when a drag event starts):
const onDragStart = (dragEvent) => {
// I added a border at this point so I can
// clearly see what's being dragged
dragEvent.currentTarget.style.border = '1px solid pink'
dragEvent.dataTransfer.setData('text/plain', dragEvent.target.id)
}
const Queen = ({ name }) => (
<li
draggable='true'
// also added a unique id so the list item can be "found"
id={`source-${name.split(' ').join('-')}`}
onDragStart={onDragStart}
>
{name}
</li>
)
Where do you want to drag it?
Next, we make another component to act as the drop target, because we gotta drop these queens somewhere, right? How about a new unordered list of FavoriteQueens
with a very conveniently-named list item called “Drop Target” so that we can visually see where we’re dragging things?
const FavoriteQueens = () => (
<ul
id='target'
onDragEnter={onDragEnter}
onDragOver={onDragOver}
onDrop={onDrop}
>
<li>Drop Target</li>
</ul>
)
For the element where we want to drop items, we will:
- identify it with an
id
- define three methods:
-
onDragEnter
to define what happens when we enter with a draggable item -
onDragOver
to define what happens when, you guessed it, we are dragging an item over -
onDrop
to define what happens when… yes, you guessed it again… we drop it!
-
For onDragEnter
, all we need to do is call dragEvent.preventDefault()
. This is because all we want to do is prevent the default behavior, which is to not allow a drag event to happen. We definitely want a drag event to happen!!!
const onDragEnter = (dragEvent) => {
dragEvent.preventDefault();
}
For onDragOver
, we want to do the same thing, and prevent the default behavior. Just for funsies, we’re also going to add a solid green border, so we can get some visual feedback and know when we’re over the draggable area.
const onDragOver = (dragEvent) => {
dragEvent.preventDefault();
dragEvent.target.style.border = '1px solid green'
}
Finally, for the onDrop
event, we start by (again) preventing the default behavior. Then, we use the getData
method on the drag event’s dataTransfer
property to retrieve the element we were just dragging. We use that data to create a new element and append it to the target. At this point, we also remove the border styles on both ends, since we’re done clicking and dragging and don’t need that visual feedback anymore. Finally, we call the clearData
method on the drag event’s dataTransfer
property to clear… the data… yes, you might have guessed as much by the name.
const onDrop = (dragEvent) => {
dragEvent.preventDefault();
const data = dragEvent.dataTransfer.getData('text');
const newElement = document.getElementById(data)
dragEvent.target.appendChild(newElement);
dragEvent.target.style.border = 'none'
newElement.style.border = 'none'
dragEvent.dataTransfer.clearData();
}
Let’s put it all together! May I present to you, the drag-and-droppable contents of App.js
in a cute lil create-react-app
application! You can view the complete code at https://github.com/julienfitz/drag-and-drop-queens
import React from 'react'
const App = () => {
const onDragStart = (dragEvent) => {
// I added a border at this point so I can
// clearly see what's being dragged
dragEvent.currentTarget.style.border = '1px solid pink'
dragEvent.dataTransfer.setData('text/plain', dragEvent.target.id)
}
const onDragEnter = (dragEvent) => {
dragEvent.preventDefault()
}
const onDragOver = (dragEvent) => {
dragEvent.preventDefault()
dragEvent.target.style.border = '1px solid green'
}
const onDrop = (dragEvent) => {
dragEvent.preventDefault()
const data = dragEvent.dataTransfer.getData('text')
const newElement = document.getElementById(data)
dragEvent.target.appendChild(newElement)
dragEvent.target.style.border = 'none'
newElement.style.border = 'none'
dragEvent.dataTransfer.clearData()
}
const Queen = ({ name }) => (
<li
draggable='true'
// also added a unique id so the list item can be "found"
id={`source-${name.split(' ').join('-')}`}
onDragStart={onDragStart}
>
{name}
</li>
)
const QueensList = () => {
const queens = [
'Divine',
'Lady Bunny',
'Sasha Velour',
'Vaginal Creme Davis',
'The Fabulous Wonder Twins'
]
return (
<ul>
{
queens.map((queenName) => (
<Queen name={queenName} />
))
}
</ul>
)
}
const FavoriteQueens = () => (
<ul
id='target'
onDragEnter={onDragEnter}
onDragOver={onDragOver}
onDrop={onDrop}
>
<li>Drop Target</li>
</ul>
)
return (
<>
<QueensList />
<FavoriteQueens />
</>
)
}
export default App
Top comments (2)
Good first post.
Helpful! I'll definitely try this in my projects.
Thanks Bruv!