Implement Better Drag and Drop in your React App
source link: https://www.tuicool.com/articles/hit/7VBZ7ju
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How To Implement Better Drag and Drop in your React App
With the react-beautiful-dnd package. Learn more.
The phrase drag-and-drop is used to describe the action of selecting an object, dragging it to wherever you desire, and then letting go or dropping it at that place on your screen.
We use drag-and-drop everyday on our device. We use it to select text in our documents. We use it to move our icons all over the screen.
The best and well known use-case of the drag-and-drop function is something called Trello .
Though drag and drop looks simple to the eye, a developer needs to take care of a lot of things in order to implement drag-and-drop in their apps. Some of these things are:
- Deciding which object should be draggable and which object should not be draggable.
- The areas in our app where the draggable objects can be dropped.
- Making sure that the drag-and-drop function does not break other functions of our apps.
- Doing all this while keeping user experience in mind.
Implementing Drag-and-Drop in our app from scratch can take a while. In this post, I will show you how to use the react-beautiful-dnd
package to easily implement a good drag-and-drop in our React app.
Shared with ❤ in Bit’s blog
Bit helps your team build faster by sharing components between apps. Collaborate, suggest updates, sync changes and build amazing apps. It’s free.
Getting Started
Let’s start by cloning this GitHub Repository that I have created.
$ git clone https://github.com/rajatk16/infinity-war.git
$ cd infinity-war
$ npm install
This will create a new React App named infinity war
. Run the start
script using NPM/Yarn to run the development server.
$ cd <project-name> $ yarn start
By now, you must have guessed that this example app here has something to do with the movie Avengers: Infinity War.
In this app, I am first going to print a list of characters from this movie, and then later on, we will see how to drag these into two lists: those who survived, and those who didn’t.
We can now see that React is listing out the name
of the Heroes
like this:
Let’s get started with adding some Drag-and-Drop functionality to our app in the next section.
Re-ordering the List
Before we get started, lets install react-beautiful-dnd
package as a dependency.
$ yarn add react-beautiful-dnd
The react-beautiful-dnd
package consists three components.
DragDropContext Droppable Draggable
Start by importing the DragDropContext
into the App/index.js
file.
import {DragDropContext} from 'react-beautiful-dnd';
We will then use this component to wrap the code that renders the columns in our app.
<DragDropContext> {this.state.columnsort.map(columnId => { const column = this.state.columns[columnId]; const heroes = column.heroId.map(heroId => this.state.heroes[heroId]); return <Column key={Column.id} column={column} heroes={heroes} />; })} </DragDropContext>
Let’s leave this as it is for now and go update the Column
component. Start by importing the Droppable
component from react-beautiful-dnd
package and use to wrap the div
with classname of Hero-List
. The Droppable
component also has one prop
named droppableId
. The Droppable
component also expects its child to be a function that returns a React component. A great advantage here is that React does not need to create any new DOM nodes.
The function here will take an argument called provided
. This argument is an object that serves a number of purposes:
-
droppableProps
areprops
that need to be applied to any component that you want to deem as draggable . -
innerRef
is a function that is used to supply the DOM node of the component to thereact-beautiful-dnd
package.
So update the Droppable
component with the following code:
<Droppable droppableId={this.props.column.id}> {(provided) => ( <div className="Hero-List" innerRef={provided.innerRef} {...provided.droppableProps} > {this.props.heroes.map((hero, index) => ( <Hero key={hero.id} hero={hero} index={index} /> ))} {provided.placeholder} </div> )} </Droppable>
The placeholder
is React element that is used to increase the available space in a droppable component while a drag is going on.
Next, we will go to the Hero
component and make it draggable. Start by importing the Draggable
component into the file.
import {Draggable} from 'react-beautiful-dnd';
Wrap the div
with className of Container
with this Draggable
component. The Draggable
component also has two props
: The draggableId
and index
.
<Draggable
draggableId={this.props.hero.id}
index={this.props.index}
>
<div className="Container">
{this.props.hero.name}
</div>
</Draggable>
Similar to the Droppable
component, the Draggable
also expects its child to be a component. So let’s take care of it by rewriting the Draggable
component as follows:
<Draggable draggableId={this.props.hero.id} index={this.props.index} > {provided => ( <div className="Container" {...provided.draggableProps} {...provided.dragHandleProps} innerRef={provided.innerRef} > {this.props.hero.name} </div> )} </Draggable>
You will now see that we can drag things around in our column like this:
But Ooops! We now see that the list doesn’t set to the new order of objects and reverts back to how it originally was.
The DragDropContext
has three callbacks:
onDragStart onDragUpdate onDragEnd
I only need the onDragEnd
callback. Let’s add it to the DragDropContext
component inside the src/App
:
<DragDropContext onDragEnd={this.onDragEnd}>
Then create the onDragEnd
helper function inside the same class as shown below:
onDragEnd = result => { const {destination, source, draggableId} = result; if (!destination) { return; } if ( destination.droppableId === source.droppableId && destination.index === source.index ) { return; } const column = this.state.columns[source.droppableId]; const newHeroIds = Array.from(column.heroIds); newHeroIds.splice(source.index, 1); newHeroIds.splice(destination.index, 0, draggableId); const newColumn = { ...column, heroIds: newHeroIds, }; const newState = { ...this.state, columns: { ...this.state.columns, [newColumn.id]: newColumn, }, }; this.setState(newState); };
Here, the source
and destination
objects contain the info about where our Draggable
started and finished.
The first conditional statement is for the case where there is no destination
. In such cases, we don’t have to do anything, so we simply return
nothing.
Next, we need to check it the location of the draggable
has changed. To do that we check whether the droppableId
and index
of the destination
and source
are the same. If they are, then that mean the location has not changed, and we don’t need to do anything again.
Then we are re-ordering the heroIds
array for that column
. First we extract the column
from the state, then I will create a new array and store it with the same data that we just extracted from the state.
After that, I am moving the heroId
from its old index
to the new index
using splice
method. I will then use splice
to insert the draggableId
, which is actually the heroId
.
Finally, we will create a whole new column that has the same properties as the old one, but with the newHeroIds
array. We will then insert this into our newState
and set that as our App’s new state.
Move Items between Columns
We can now move objects vertically within the same column. But what about moving things to other columns? We haven’t implemented that functionality yet.
Let’s start by creating a new Container
component in the src/App
file.
const Container = styled.div` display: flex; `;
Use this component to wrap the column-related code inside the class.
<Container> {this.state.columnsort.map(columnId => { const column = this.state.columns[columnId]; const heroes = column.heroIds.map(heroId => this.state.heroes[heroId]); return <Column key={Column.id} column={column} heroes={heroes} />; })} </Container>
This will render the columns horizontally instead of vertically.
But as you can see, the second and third columns are way too small. To fix this tiny issue, go to the Column/index.js
file and add a width
property to the Container
component. We will also add some flexbox properties
const Container = styled.div`
margin: 10px;
border: 1px solid lightgrey;
border-radius: 5px;
width: 33%;
display: flex
flex-direction: column
`;
This has equalized width of all three columns and created a flexbox whose children are vertically aligned. Similarly, I want to make the HeroList
component to “grow”.
const HeroList = styled.div` padding: 10px; flex-grow: 1; min-height: 100px; `;
We now face the same issue that we before. When we drag and drop an object into another column, it reverts back to the same column. Let’s fix this by making some changes to the onDragEnd
helper function in the App/index.js
file.
Here we had created a const named column
. Rename this to something like begin
and create another const named end
.
const begin = this.state.columns[source.droppableId];
const end = this.state.columns[destination.droppableId];
Now if both the begin
and end
are the same, that means we are doing a drag and drop in the same column. So we can use the same code that we were using before, just wrapped inside a if
statement.
if (begin === end) {
const newHeroIds = Array.from(begin.heroIds);
newHeroIds.splice(source.index, 1);
newHeroIds.splice(destination.index, 0, draggableId);
const newColumn = {
...begin,
heroIds: newHeroIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newColumn.id]: newColumn,
},
};
this.setState(newState);
return;
}
Now let’s take care of the scenario where begin
and end
are not the same, meaning, the user is trying to move an object from one column to another.
Create a new array named beginHeroIds
that contains the same ids as the old array. We will then remove the dragged heroId
from this array, create a newBegin
column, put the same properties in it as the old column along with the newBeginIds
array.
const beginHeroIds = Array.from(begin.heroIds); beginHeroIds.splice(source.index, 1); const newBegin = { ...begin, heroIds: beginHeroIds };
Similarly, we will create another array named endHeroIds
that contains the same heroIds
as the last end
column. We will use the splice
operator to insert the draggableId
at the destination.index
. We will then create another new column named newEnd
.
We will then create a newState
object which will have the same properties as the old state, but with updated columns.
const newState = { ...this.state, columns: { ...this.state.columns, [newBegin.id]: newBegin, [newEnd.id]: newEnd, }, }; this.setState(newState);
With this, we can now drag and drop our object in different columns.
Conclusion
We can do a lot more with this awesome package. For instance, we can drag and drop entire columns, or disable the drag and drop for certain objects in a column.
Drap and Drops are great. They help improve the user experience significantly. I hope this post helped you understand how to implement Drag and Drop in your React App.
You can check out the final version of this app here:
I am Rajat S . An aspiring coder who has a long way to go. A Die-Hard DC Comics Fan who loves Marvel Movies. :stuck_out_tongue: Thank you for reading! Please do :clap: if you liked it and feel free to comment and ask anything!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK