Multiple Draggable and Sortable Lists in React using react-beautiful-dnd Tutorial with Examples

In this React tutorial, we’ll learn how to build lists with draggable and sortable items. Using the react-beautiful-dnd package library, we can create animates and fully customizable lists with lots of features.

Sortable lists are used to add items that can be dragged to interchange the position by simply dragging them over the list or between multiple lists.

Draggable items can have multiple use-cases in a real-world application. Like sorting list of tasks, songs in queue, setting the priority of events, video playlist items, etc.

We’ll learn how to install this package and start using it to create sortable lists in the step by step tutorial with examples for supported features.

 

 

Create a React Application

First, we’ll create a new React application using npx create-react-app command

$ npx create-react-app react-beautiful-dnd-app

Move inside the react app

$ cd react-beautiful-dnd-app

Run application

$ npm start

 

Install react-beautiful-dnd Package

After creating the React application ready, install the react-beautiful-dnd package by running below npm command

$ npm install react-beautiful-dnd --save

 

Creating Sortable List

We’ll create a new component file in this location ‘~src/components/vertical-dnd.component.js’

To create a sortable list, we need to import DragDropContext, Droppable and Draggable component class from "react-beautiful-dnd"

Each component plays its role in building a sortable list:

  • <DragDropContext/> : It is added as a wrapper for the Droppable and its Draggable items.
  • <Droppable/> : Defines the area where the items can be dropped by the user.
  • <Draggable/> : Each draggable item in the list is wrapped inside this component.

Update the vertical-dnd.component.js file as shown below:

// 
import React, { Component } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";

// Create data for list
const getItems = count =>
    Array.from({ length: count }, (v, k) => k).map(k => ({
        id: `task-${k}`,
        content: `Task ${k}`
    }));

// Reorder the list items
const reorder = (list, startIndex, endIndex) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);

    return result;
};

const grid = 10;

const getItemStyle = (isDragging, draggableStyle) => ({
    userSelect: "none",
    padding: grid * 2,
    margin: `0 0 ${grid}px 0`,
    background: isDragging ? "#77e2e0" : "#33c9c7",
    ...draggableStyle
});

const getListStyle = isDraggingOver => ({
    background: isDraggingOver ? "#a1ffc5" : "#daffff",
    padding: grid,
    width: 250
});

class VerticalDragList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: getItems(10)
        };
        this.onDragEnd = this.onDragEnd.bind(this);
    }

    onDragEnd(result) {
        // if item dropped outside the list
        if (!result.destination) {
            return;
        }

        const items = reorder(
            this.state.items,
            result.source.index,
            result.destination.index
        );

        this.setState({
            items
        });
    }

    render() {
        return (
            <DragDropContext onDragEnd={this.onDragEnd}>
                <Droppable droppableId="droppable">
                    {(provided, snapshot) => (
                        <div
                            {...provided.droppableProps}
                            ref={provided.innerRef}
                            style={getListStyle(snapshot.isDraggingOver)}
                        >
                            {this.state.items.map((item, index) => (
                                <Draggable key={item.id} draggableId={item.id} index={index}>
                                    {(provided, snapshot) => (
                                        <div
                                            ref={provided.innerRef}
                                            {...provided.draggableProps}
                                            {...provided.dragHandleProps}
                                            style={getItemStyle(
                                                snapshot.isDragging,
                                                provided.draggableProps.style
                                            )}
                                        >
                                            {item.content}
                                        </div>
                                    )}
                                </Draggable>
                            ))}
                            {provided.placeholder}
                        </div>
                    )}
                </Droppable>
            </DragDropContext>
        );
    }
}

export default VerticalDragList;

The items property is having collection tasks generated in the getItems() function.

The reorder() function is called inside the onDragEnd() event handler on <DragDropContext/> component.

The getItemStyle and getItemList functions are returning overridden styles for the list and its items.

The Draggable component is repeated with items of the list using map() function.

 

Next, just place the VerticalDragList component inside the App.js file componet

import React from 'react';
import './App.css';

import VerticalDragList from './components/vertical-dnd.component';

function App() {
  return (
    <div className="App">

      <VerticalDragList />

    </div>
  );
}

export default App;

 

 

Sorting Between Two/ Multiple List

Items can be dragged in the same or between two different lists. For multiple lists, we’ll have two <Droppable/> components each having a different value for droppableId property.

Create a new component at this location ‘~src/components/multiple-list-dnd.component.js’

import React, { Component } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const getItems = (count, offset = 0) =>
    Array.from({ length: count }, (v, k) => k).map(k => ({
        id: `item-${k + offset}`,
        content: `item ${k + offset}`
    }));

const reorder = (list, startIndex, endIndex) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);

    return result;
};

// Move item from one list to other
const move = (source, destination, droppableSource, droppableDestination) => {
    const sourceClone = Array.from(source);
    const destClone = Array.from(destination);
    const [removed] = sourceClone.splice(droppableSource.index, 1);

    destClone.splice(droppableDestination.index, 0, removed);

    const result = {};
    result[droppableSource.droppableId] = sourceClone;
    result[droppableDestination.droppableId] = destClone;

    return result;
};

const grid = 10;

const getItemStyle = (isDragging, draggableStyle) => ({
    userSelect: 'none',
    padding: grid * 2,
    margin: `0 0 ${grid}px 0`,

    background: isDragging ? 'lightgreen' : 'grey',

    ...draggableStyle
});

const getListStyle = isDraggingOver => ({
    background: isDraggingOver ? 'lightblue' : 'lightgrey',
    padding: grid,
    width: 250
});

class MultipleDragList extends Component {
    state = {
        items: getItems(10),
        selected: getItems(5, 10)
    };

    // Defining unique ID for multiple lists
    id2List = {
        droppable: 'items',
        droppable2: 'selected'
    };

    getList = id => this.state[this.id2List[id]];

    onDragEnd = result => {
        const { source, destination } = result;

        if (!destination) {
            return;
        }

        // Sorting in same list
        if (source.droppableId === destination.droppableId) {
            const items = reorder(
                this.getList(source.droppableId),
                source.index,
                destination.index
            );

            let state = { items };

            if (source.droppableId === 'droppable2') {
                state = { selected: items };
            }

            this.setState(state);
        }
        // Interlist movement
        else {
            const result = move(
                this.getList(source.droppableId),
                this.getList(destination.droppableId),
                source,
                destination
            );

            this.setState({
                items: result.droppable,
                selected: result.droppable2
            });
        }
    };

    render() {
        return (
            <div style={{ 'display': 'flex' }}>
                <DragDropContext onDragEnd={this.onDragEnd}>
                    <Droppable droppableId="droppable">
                        {(provided, snapshot) => (
                            <div
                                ref={provided.innerRef}
                                style={getListStyle(snapshot.isDraggingOver)}>
                                {this.state.items.map((item, index) => (
                                    <Draggable
                                        key={item.id}
                                        draggableId={item.id}
                                        index={index}>
                                        {(provided, snapshot) => (
                                            <div
                                                ref={provided.innerRef}
                                                {...provided.draggableProps}
                                                {...provided.dragHandleProps}
                                                style={getItemStyle(
                                                    snapshot.isDragging,
                                                    provided.draggableProps.style
                                                )}>
                                                {item.content}
                                            </div>
                                        )}
                                    </Draggable>
                                ))}
                                {provided.placeholder}
                            </div>
                        )}
                    </Droppable>
                    <Droppable droppableId="droppable2">
                        {(provided, snapshot) => (
                            <div
                                ref={provided.innerRef}
                                style={getListStyle(snapshot.isDraggingOver)}>
                                {this.state.selected.map((item, index) => (
                                    <Draggable
                                        key={item.id}
                                        draggableId={item.id}
                                        index={index}>
                                        {(provided, snapshot) => (
                                            <div
                                                ref={provided.innerRef}
                                                {...provided.draggableProps}
                                                {...provided.dragHandleProps}
                                                style={getItemStyle(
                                                    snapshot.isDragging,
                                                    provided.draggableProps.style
                                                )}>
                                                {item.content}
                                            </div>
                                        )}
                                    </Draggable>
                                ))}
                                {provided.placeholder}
                            </div>
                        )}
                    </Droppable>
                </DragDropContext>
            </div>
        );
    }
}

export default MultipleDragList;

The move() will take care to remove the item from one list and push it to another list. Which is called from onDragEnd event listener while checking if the item is dropped in the same list or in another list.

Update the App component

import React from 'react';
import './App.css';

import VerticalDragList from './components/vertical-dnd.component';
import MultipleDragList from './components/multiple-list-dnd.component';

function App() {
  return (
    <div className="App">

      <h3>Single Vertical List</h3>
      <VerticalDragList />

      <h3>Multiple Vertical Lists</h3>
      <MultipleDragList />

    </div>
  );
}

export default App;

Now run the application by hitting $ npm start

 

Using react-beautiful-dnd you can create awesome draggable lists. It can have multiple features like Virtual list, Multiple selections of items to drag, Sorting between parent-children hierarchy, Combining more than one item in a list into a single, etc.

You can check the official documentation here. Also, check the list of all working examples here.

 

Conclusion

Here we discussed basic verticle sortable list within a single or multiple lists. There are number of use cases and features available that can be implemented very easily.

 

 

 

Leave a Comment

Your email address will not be published. Required fields are marked *