DEV Community

Cover image for Pop, Lock, (Drag) and Drop
Julien Fitzpatrick
Julien Fitzpatrick

Posted on

Pop, Lock, (Drag) and Drop

Alt Text

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

a gif of Divine

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    

Discussion (2)

Collapse
steveblue profile image
Steve Belovarich • Edited on

Good first post.

Collapse
hugekontrast profile image
Ashish Khare😎

Helpful! I'll definitely try this in my projects.
Thanks Bruv!