Drag & Drop re-ordering using HTML and React
Tags: javascript, react, html
Published: 2020-10-13 17:05:09

Working with javascript these days, you will often run into a scenario where you want to render a list of items. But what happens when you want your user to be able to re-order those items on the fly? Well here I'm going to show you how to use HTML5's Drag and Drop (DnD) API, with React to easily let your users move things around 'til their heart's content.

###First, we're going to need a list of things to render!

We'll start with a simple React App that renders 3 colourful boxes on the screen.

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

App.js
```
import React, { useState } from "react";
import Box from "./Box";
import "./styles.css";

const App = () => {
  const [boxes, setBoxes] = useState([
    {
      id: "Box-1",
      color: "red",
      order: 1
    },
    {
      id: "Box-2",
      color: "green",
      order: 2
    },
    {
      id: "Box-3",
      color: "blue",
      order: 3
    }
  ]);

  return (
    <div className="App">
      {boxes
        .sort((a, b) => a.order - b.order)
        .map((box) => (
          <Box
            key={box.id}
            boxColor={box.color}
            boxNumber={box.id}
          />
        ))}
    </div>
  );
}

export default App;
```

Box.js
```
import React from "react";

const Box = ({ boxColor, boxNumber }) => {
  return (
    <div
      id={boxNumber}
      style={{
        backgroundColor: boxColor,
        border: "1px solid",
        borderColor: boxColor,
        borderRadius: "5px",
        color: "#FFF",
        width: "30%",
        height: "100px"
      }}
    >
      {boxNumber}
    </div>
  );
};

export default Box;

```

This should render your boxes like the ones in the picture above. But they don't do anything yet!

> Note the way the boxes are sorted to be rendered based on the `order` attribute in their objects

### Next step is going to be introducing the DnD API into our boxes.
To do this, we're going to go back into Box.js and add some attributes to the `<div>`. We're going to change it to: 
```
const Box = ({ boxColor, boxNumber, handleDrag, handleDrop }) => {
  return (
    <div
      draggable={true}
      id={boxNumber}
      onDragOver={(ev) => ev.preventDefault()}
      onDragStart={handleDrag}
      onDrop={handleDrop}
      style={{
        backgroundColor: boxColor,
        border: "1px solid",
        borderColor: boxColor,
        borderRadius: "5px",
        color: "#FFF",
        width: "30%",
        height: "100px"
      }}
    >
      {boxNumber}
    </div>
  );
};

```
First thing to note is that we are now taking in two extra props, `handleDrag` and `handleDrop`. These are just functions we are going to pass down from App.js to handle what happens when you drag and drop the box respectively.

We've also added some attributes to the `<div>`.
I'm not going to do into too much detail about what each of these attributes does, but briefly:
* `draggable` sets whether the element can be dragged or not;
* `onDragStart` is an event listener triggered when you start dragging the element;
* `onDrop` is an event listener triggered when you drop the element;
* `onDragOver` is an event listener triggered when you drag the element over something else;

We're going to set `onDragStart` to the `handleDrag` prop we've just passed in, and `onDrop` to the `handleDrop` prop.

For `onDragOver` we're going to set a function to prevent the browser's default action, which is usually to attempt to navigate to a link or something like that.


### Now for App.js.
Here we're going to add in the `handleDrag` and `handleDrop` functions, and then we're going to pass them down into the Box component. 

So we'll take these one at a time, starting with `handleDrag`:
```
  const [dragId, setDragId] = useState();

  const handleDrag = (ev) => {
    setDragId(ev.currentTarget.id);
  };
```

We've added a new state variable called `dragId` to keep track of which box it is we're currently dragging. Inside the `handleDrag` function itself all we're doing is getting the box id from the event and setting it to state.

`handleDrop` is the more complicated of the two functions, and this is where we will handle all of our 'switching' code.
```
  const handleDrop = (ev) => {
    const dragBox = boxes.find((box) => box.id === dragId);
    const dropBox = boxes.find((box) => box.id === ev.currentTarget.id);

    const dragBoxOrder = dragBox.order;
    const dropBoxOrder = dropBox.order;

    const newBoxState = boxes.map((box) => {
      if (box.id === dragId) {
        box.order = dropBoxOrder;
      }
      if (box.id === ev.currentTarget.id) {
        box.order = dragBoxOrder;
      }
      return box;
    });

    setBoxes(newBoxState);
  };
```
Here, we first want to identify which box is being dragged and which box it has been dropped on. We do this using the array `find()` method and comparing each box id with the `dragId` (that we set in `handleDrag`) for the box being dragged, and with the id of the element emitting the event for the box being dropped on.

Because we're going to change the order of the boxes, we don't want the original order of our two boxes to be changed, so we're going to take note of that in the `dragBoxOrder` and `dropBoxOrder` variables.

Finally, we're going to the actual switch.
```
    const newBoxState = boxes.map((box) => {
      if (box.id === dragId) {
        box.order = dropBoxOrder;
      }
      if (box.id === ev.currentTarget.id) {
        box.order = dragBoxOrder;
      }
      return box;
    });
```
We're going to use the array `map()` function to allow us to rearrange the boxes order and return as a new array. Inside the `map()` function we'll check if the current box's id is equal to the dragId. If it is, set its order to be the dropBoxOrder. If not, check if it is equal to the id of the box being dropped on, and if that is true set its order to be the dragBoxOrder.

So when the `map()` function has stopped running we should have a new array in the `newBoxState` variable where the order variable for the two boxes involved has been swapped. We can then set this new array of box objects to state, and trigger the re-render.

To see the complete code, or play with the completed demo, check out this codesandbox:
https://codesandbox.io/s/react-drag-drop-reorder-mxt4t?fontsize=14&hidenavigation=1&theme=dark