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.
[lwptoc]
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 theDroppable
and itsDraggable
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 Reply