Pop, Lock, (Drag) and Drop
Tags: html, javascript, react, webdev
Published: 2020-08-23 03:44:41

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mhxgrl5ci21ennjzgr89.gif)

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](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - 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

![a gif of Divine](https://media2.giphy.com/media/vfPjzIhsgLNNm/giphy.gif)

```
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    
```