React drag and drop list of elements

Learn how to drag and drop list or grid of elements in react.

We will create a component in which its elements can be sorted by changing their order with the help of drag and drop.

For this we will be needing few extra packages.

  • classnames: This helps us to use CSS classes as javascript objects, we just need to name our CSS file as filename.module.css.
  • @fortawesome: Fontawesome package which helps to use free icons as a react component.
  • react-sortable-hoc: This package helps us with drag and drop in react.

Following is the folder structure of our component.
React drag and drop folder structure

React drag and drop list component.

Let us begin developing our component by importing all the required packages at the top.

import React, { Component } from "react";
import {
  sortableContainer,
  sortableElement,
  sortableHandle
} from "react-sortable-hoc";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBars } from "@fortawesome/free-solid-svg-icons";
import styles from "./index.module.css";
import arrayMove from "./arrayMove";

class SortableItems extends Component {
  //Other code will go here
}

export default SortableItems;

As you can already see we have imported many components. Let’s break them and understand each one by one.

Drag and drop package

  • sortableContainer: Area where drag and drop will take place.
  • sortableElement: An element which will be draggable.
  • sortableHandle: Entry point using which the drag operation will start.

Fontawesome

  • FontAwesomeIcon: Generates the provided icon.
  • faBars: Icon

ArrayMove

This helps us to mutate the index of the array elements.


So as you may have got some idea of how this drag and drop will work, but let me try my bit to explain it.

Basically we will need a handler using which we will drag the items. Only items that are inside the drag area can be dragged so we have to wrap them inside it.

You can even wrap whole element inside the handler that way the element itself will become draggable.

We are going to create a separate functional component for each of them which will use these imported component and generate the required component.

//Drag handler
const DragHandle = sortableHandle(() => (
  <span className={styles.dragHandler}>
    <FontAwesomeIcon icon={faBars} />
  </span>
));

//Draggable elements
const SortableItem = sortableElement(({ value }) => (
  <div className={styles.dragElement}>
    {value}
    <DragHandle />
  </div>
));

//Drag area
const SortableContainer = sortableContainer(({ children }) => {
  return <div className={styles.dragContainer}>{children}</div>;
});

We are using hamburger icon from the fontawesome as our drag handler and have placed it inside the element. Then we are passing these generated elements to the drag area.

Now it is time to create our component and render the items.

First, create the state so that we can manage the items whenever their order are changed.

  constructor(props) {
    super(props);
    this.state = {
      items: ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"]
    };
  }

Then render these items.

 render() {
    const { items } = this.state;

    return (
      <SortableContainer onSortEnd={this.onSortEnd} useDragHandle>
        {items.map((value, index) => (
          <SortableItem key={`item-${index}`} index={index} value={value} />
        ))}
      </SortableContainer>
    );
  }

There is a callback function onSortEnd which is called every time an element is dragged and dropped successfully.

In this function we receive the old index of the element and the new index where item is placed. We will have to use this and then update the items list accordingly in our state.

onSortEnd = ({ oldIndex, newIndex }) => {
 this.setState(({ items }) => ({
   items: arrayMove(items, oldIndex, newIndex)
 }));
};

Our arrayMove function which we have imported helps us to do this. Below is the code for it.

//arrayMove.js
const arrayMoveMutate = (array, from, to) => {
  array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
};

const arrayMove = (array, from, to) => {
  array = array.slice();
  arrayMoveMutate(array, from, to);
  return array;
};

export default arrayMove;

Complete code of react drag and drop list component.

import React, { Component } from "react";
import {
  sortableContainer,
  sortableElement,
  sortableHandle
} from "react-sortable-hoc";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBars } from "@fortawesome/free-solid-svg-icons";
import styles from "./index.module.css";
import arrayMove from "./arrayMove";

//Drag handler
const DragHandle = sortableHandle(() => (
  <span className={styles.dragHandler}>
    <FontAwesomeIcon icon={faBars} />
  </span>
));

//Draggable elements
const SortableItem = sortableElement(({ value }) => (
  <div className={styles.dragElement}>
    {value}
    <DragHandle />
  </div>
));

//Drag area
const SortableContainer = sortableContainer(({ children }) => {
  return <div className={styles.dragContainer}>{children}</div>;
});

class SortableItems extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"]
    };
  }

  onSortEnd = ({ oldIndex, newIndex }) => {
    this.setState(({ items }) => ({
      items: arrayMove(items, oldIndex, newIndex)
    }));
  };

  render() {
    const { items } = this.state;

    return (
      <SortableContainer onSortEnd={this.onSortEnd} useDragHandle>
        {items.map((value, index) => (
          <SortableItem key={`item-${index}`} index={index} value={value} />
        ))}
      </SortableContainer>
    );
  }
}

export default SortableItems;

ArrayMove

//arrayMove.js
const arrayMoveMutate = (array, from, to) => {
  array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
};

const arrayMove = (array, from, to) => {
  array = array.slice();
  arrayMoveMutate(array, from, to);
  return array;
};

export default arrayMove;

Complete style code of sortable grid component.

Our third party package does not provide any styles, we are free to style our component as per our need.

I have kept the style completely simple.

.dragContainer {
  width: 90%;
  margin: 0 auto;
}

.dragHandler {
  padding: 1px 5px;
  color: #000;
  background: #ffffff;
  border-radius: 3px;
  cursor: grab;
  border: 1px solid;
}

.dragElement {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  margin-bottom: 10px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5);
}

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import SortableItems from "./Components/SortableItems";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <SortableItems />
  </div>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Output

React drag and drop list